Some housekeeping: In case you don’t have the necessary packages installed, run this script to do so.

Machine Learning: Some clarifications

General

As with any concept, machine learning may have a slightly different definition, depending on whom you ask. A little compilation of definitions by academics and practioneers alike:

  • “Machine Learning at its most basic is the practice of using algorithms to parse data, learn from it, and then make a determination or prediction about something in the world.” - Nvidia
  • “Machine learning is the science of getting computers to act without being explicitly programmed.” - Stanford
  • “Machine learning is based on algorithms that can learn from data without relying on rules-based programming.”- McKinsey & Co.
  • “Machine learning algorithms can figure out how to perform important tasks by generalizing from examples.” - University of Washington
  • “The field of Machine Learning seeks to answer the question”How can we build computer systems that automatically improve with experience, and what are the fundamental laws that govern all learning processes?" - Carnegie Mellon University

Supervised vs. Unsupervised ML

An intuitive perspective

A functional perspective

Overview over types and classes of algorithms

Sometimes, the lines are blurry, given the vast amount of algorithms and techniques, which constantly expands.

Supervised ML

  • Concerned with labeling/classification/input-output-mapping/prediction tasks
  • Subject of the next lecture, so stay patient

Unsupervised ML

Tasks related to pattern recognition and data exploration, in dase there yet does not exist a right answer or problem structure. Main application

  1. Dimensionality reduction: Finding patterns in the features of the data
  2. Clustering: Finding homogenous subgroups within larger group

Dimensionality Reduction Techniques

Introduction

Dimensionality reduction techniques are foremost useful to (you might see it coming) reduce the dimensionality of our data. So, what does that mean? And why should we want to do that?

Dimensions here is a synonym for variables, so what we want to really do is have less variables. To do that, we have to find ways to express the same amount of information with fewer, but more information rich variables. This is particularly useful to:

  • Find patterns in the features of the data
  • Visualization of high-dimensional data
  • Pre-processing before supervised learning

Overview over Techniques

The type of analysis to be performed depends on the data set formats and structures. The most commonly used DR techniques are:

  • Principal Component Analysis (PCA): Is used to summarize the information contained in a continuous (i.e, quantitative) multivariate data by reducing the dimensionality of the data without loosing important information.
  • Correspondence Analysis (CA): An extension of the principal component analysis suited to analyse a large contingency table formed by two qualitative variables (or categorical data).
  • Multiple Correspondence Analysis (MCA): An adaptation of CA to a data table containing more than two categorical variables.
  • Multiple Factor Analysis (MFA): Dedicated to datasets where variables are organized into groups (qualitative and/or quantitative variables).
  • Hierarchical Multiple Factor Analysis (HMFA): An extension of MFA in a situation where the data are organized into a hierarchical structure.
  • Factor Analysis of Mixed Data (FAMD): A particular case of the MFA, dedicated to analyze a data set containing both quantitative and qualitative variables.

Principal Component Analysis (PCA)

General

  • A popular method is principal component analysis (PCA)
  • Three goals when finding lower dimensional representation of features:
    1. Find linear combination of variables to createprincipal components
    2. Maintain most variance in the data
    3. Principal components are uncorrelated (i.e.orthogonal to each other)

The math and intuition behind it

The mathematics underlying it are somewhat complex, so I won’t go into too much detail, but the basics of PCA are as follows: you take a dataset with many variables, and you simplify that dataset by turning your original variables into a smaller number of “Principal Components”.

But what are these exactly? Principal Components are the underlying structure in the data. They are the directions where there is the most variance, the directions where the data is most spread out. This means that we try to find the straight line that best spreads the data out when it is projected along it. This is the first principal component, the straight line that shows the most substantial variance in the data.

Where many variables correlate with one another, they will all contribute strongly to the same principal component. Each principal component sums up a certain percentage of the total variation in the dataset. Where your initial variables are strongly correlated with one another, you will be able to approximate most of the complexity in your dataset with just a few principal components. Usually, the first principal component captures the main similarity in your data, the second the main difference.

These principal components can be computed via Eigenvalues and Eigenvectors. Just like many things in life, eigenvectors, and eigenvalues come in pairs: every eigenvector has a corresponding eigenvalue. Simply put, an eigenvector is a direction, such as “vertical” or “45 degrees”, while an eigenvalue is a number telling you how much variance there is in the data in that direction. The eigenvector with the highest eigenvalue is, therefore, the first principal component. The number of eigenvalues and eigenvectors that exits is equal to the number of dimensions the data set has. Consequently, we can reframe a dataset in terms of these eigenvectors and eigenvalues without changing the underlying information.

Note that reframing a dataset regarding a set of eigenvalues and eigenvectors does not entail changing the data itself, you’re just looking at it from a different angle, which should represent the data better.

Case study: (Digital) Nomad Life

O & S & E: Load, clean and inspect

Allright, lets load some data. Here, we will draw from some own work, where we explore the life of digital nomads. The paper is not written, but the preliminary work is summarized in this presentation. You probably already know the data from NomadList. Here, we look at the 2017 crawl of city data, which compiles the digital nomads ranking of cities according to a couple of dimensions. Lets take a look.

Roman’s web-crawld ata is always a bit messy, so we do some little cosmetics upfront.

Lets take a look:

Quite a set of interesting features, which are all numerically coded. Lets select the one we want to analyze and organize them a bit. Since it’s a lot of variables, I afterwards select only a subset on which we do some graphical exploration.

Ok, time for some exploration. Here I will introduce the GGally package, a wrapper for ggplot2 which has some functions for very nice visual summaries in matrix form.


Attaching package: <U+393C><U+3E31>GGally<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:dplyr<U+393C><U+3E32>:

    nasa

First, lets look at a classical correlation matrix.

Even cooler, the ggpairs function creates you a scatterplot matrix plus all variable distributions and correlations. Before I used the package PerformanceAnalytics for that, but I like the ggplot-style more.


 plot: [1,1] [===----------------------------------------------------------------------------------------------]  3% est: 0s 
 plot: [1,2] [=====--------------------------------------------------------------------------------------------]  6% est: 2s 
 plot: [1,3] [========-----------------------------------------------------------------------------------------]  8% est: 3s 
 plot: [1,4] [===========--------------------------------------------------------------------------------------] 11% est: 3s 
 plot: [1,5] [=============------------------------------------------------------------------------------------] 14% est: 3s 
 plot: [1,6] [================---------------------------------------------------------------------------------] 17% est: 3s 
 plot: [2,1] [===================------------------------------------------------------------------------------] 19% est: 2s 
 plot: [2,2] [======================---------------------------------------------------------------------------] 22% est: 3s 
 plot: [2,3] [========================-------------------------------------------------------------------------] 25% est: 3s 
 plot: [2,4] [===========================----------------------------------------------------------------------] 28% est: 2s 
 plot: [2,5] [==============================-------------------------------------------------------------------] 31% est: 2s 
 plot: [2,6] [================================-----------------------------------------------------------------] 33% est: 2s 
 plot: [3,1] [===================================--------------------------------------------------------------] 36% est: 2s 
 plot: [3,2] [======================================-----------------------------------------------------------] 39% est: 2s 
 plot: [3,3] [========================================---------------------------------------------------------] 42% est: 2s 
 plot: [3,4] [===========================================------------------------------------------------------] 44% est: 2s 
 plot: [3,5] [==============================================---------------------------------------------------] 47% est: 2s 
 plot: [3,6] [================================================-------------------------------------------------] 50% est: 2s 
 plot: [4,1] [===================================================----------------------------------------------] 53% est: 2s 
 plot: [4,2] [======================================================-------------------------------------------] 56% est: 2s 
 plot: [4,3] [=========================================================----------------------------------------] 58% est: 2s 
 plot: [4,4] [===========================================================--------------------------------------] 61% est: 1s 
 plot: [4,5] [==============================================================-----------------------------------] 64% est: 1s 
 plot: [4,6] [=================================================================--------------------------------] 67% est: 1s 
 plot: [5,1] [===================================================================------------------------------] 69% est: 1s 
 plot: [5,2] [======================================================================---------------------------] 72% est: 1s 
 plot: [5,3] [=========================================================================------------------------] 75% est: 1s 
 plot: [5,4] [===========================================================================----------------------] 78% est: 1s 
 plot: [5,5] [==============================================================================-------------------] 81% est: 1s 
 plot: [5,6] [=================================================================================----------------] 83% est: 1s 
 plot: [6,1] [====================================================================================-------------] 86% est: 1s 
 plot: [6,2] [======================================================================================-----------] 89% est: 0s 
 plot: [6,3] [=========================================================================================--------] 92% est: 0s 
 plot: [6,4] [============================================================================================-----] 94% est: 0s 
 plot: [6,5] [==============================================================================================---] 97% est: 0s 
 plot: [6,6] [=================================================================================================]100% est: 0s 
                                                                                                                             

Digression: Missing value imputation

To remind you, component scores cannot be computed on missing features. So lets impute them. It’s a good point to introduce you to some neath imputation techniques. First, the package VIM has some nice imputation functions, but also some nice diagnistic plots.

Loading required package: colorspace
Loading required package: grid
VIM is ready to use. 
 Since version 4.0.0 the GUI is in its own package VIMGUI.

          Please use the package to use the new (and old) GUI.

Suggestions and bug-reports can be submitted at: https://github.com/alexkowa/VIM/issues

Attaching package: <U+393C><U+3E31>VIM<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:datasets<U+393C><U+3E32>:

    sleep

 Variables sorted by number of missings: 

For the real imputation, I prefer the mice package, which works with a neural network “under the hood”. Here, every feature is sequentially predicted by all other existing features in an iterative process. Since this process involves some stochachics, I define a seed upfront for reproducible results.


 iter imp variable
  1   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  2   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  3   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  4   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  5   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  6   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  7   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  8   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  9   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  10   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  11   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  12   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  13   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  14   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  15   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  16   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  17   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  18   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  19   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  20   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  21   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  22   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  23   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  24   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  25   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  26   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  27   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  28   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  29   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  30   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  31   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  32   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  33   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  34   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  35   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  36   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  37   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  38   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  39   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  40   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  41   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  42   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  43   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  44   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  45   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  46   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  47   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  48   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  49   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  50   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  51   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  52   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  53   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  54   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  55   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  56   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  57   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  58   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  59   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  60   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  61   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  62   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  63   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  64   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  65   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  66   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  67   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  68   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  69   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  70   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  71   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  72   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  73   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  74   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  75   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  76   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  77   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  78   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  79   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  80   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  81   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  82   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  83   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  84   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  85   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  86   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  87   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  88   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  89   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  90   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  91   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  92   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  93   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  94   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  95   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  96   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  97   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  98   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  99   1  freedom_score  peace_score  fragile_states_index  press_freedom_index
  100   1  freedom_score  peace_score  fragile_states_index  press_freedom_index

Let’s look at the distribution of the imputed vs. the existing features.

I would say, good enough. Lets take them!

Dimensionality reduction

To execute the PCA, we’ll here use the FactoMineR package to compute PCA, and factoextra for extracting and visualizing the results. FactoMineR is a great and my favorite package for computing principal component methods in R. It’s very easy to use and very well documented. There are other alternatives around, but I since quite some time find it to be the most powerful and convenient one. factoextra is just a convenient ggplot wrapper that easily produces nice and informative diagnistic plots for a variety of DR and clustering techniques.

Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa

Lets do that. Notice the scale.unit = TRUE argument, which you should ALWAYS use. Afterwards, we take a look at the resulting list object.

List of 5
 $ eig : num [1:21, 1:3] 9.062 1.976 1.312 1.125 0.931 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:21] "comp 1" "comp 2" "comp 3" "comp 4" ...
  .. ..$ : chr [1:3] "eigenvalue" "percentage of variance" "cumulative percentage of variance"
 $ var :List of 4
  ..$ coord  : num [1:21, 1:5] 0.656 0.421 0.595 0.747 0.747 ...
  .. ..- attr(*, "dimnames")=List of 2
  ..$ cor    : num [1:21, 1:5] 0.656 0.421 0.595 0.747 0.747 ...
  .. ..- attr(*, "dimnames")=List of 2
  ..$ cos2   : num [1:21, 1:5] 0.431 0.177 0.354 0.559 0.559 ...
  .. ..- attr(*, "dimnames")=List of 2
  ..$ contrib: num [1:21, 1:5] 4.75 1.95 3.9 6.16 6.16 ...
  .. ..- attr(*, "dimnames")=List of 2
 $ ind :List of 4
  ..$ coord  : num [1:781, 1:5] 0.465 -2.067 2.273 1.936 4.243 ...
  .. ..- attr(*, "dimnames")=List of 2
  ..$ cos2   : num [1:781, 1:5] 0.0125 0.308 0.2003 0.1424 0.3431 ...
  .. ..- attr(*, "dimnames")=List of 2
  ..$ contrib: num [1:781, 1:5] 0.00306 0.06037 0.07297 0.05294 0.25431 ...
  .. ..- attr(*, "dimnames")=List of 2
  ..$ dist   : Named num [1:781] 4.16 3.72 5.08 5.13 7.24 ...
  .. ..- attr(*, "names")= chr [1:781] "1" "2" "3" "4" ...
 $ svd :List of 3
  ..$ vs: num [1:21] 3.01 1.406 1.145 1.061 0.965 ...
  ..$ U : num [1:781, 1:5] 0.154 -0.687 0.755 0.643 1.409 ...
  ..$ V : num [1:21, 1:5] 0.218 0.14 0.198 0.248 0.248 ...
 $ call:List of 9
  ..$ row.w     : num [1:781] 0.00128 0.00128 0.00128 0.00128 0.00128 ...
  ..$ col.w     : num [1:21] 1 1 1 1 1 1 1 1 1 1 ...
  ..$ scale.unit: logi TRUE
  ..$ ncp       : num 5
  ..$ centre    : num [1:21] 2332.3 210.4 1880.4 3.3 3.3 ...
  ..$ ecart.type: num [1:21] 1116.82 173.87 1264.6 1.98 1.98 ...
  ..$ X         :'data.frame':  781 obs. of  21 variables:
  .. ..$ cost_nomad            : int [1:781] 1364 777 1639 1545 3028 3238 2554 3503 3427 2245 ...
  .. ..$ cost_coworking        : num [1:781] 152.4 98.9 159.1 47 200 ...
  .. ..$ cost_expat            : int [1:781] 1273 780 1653 1640 3309 4325 2197 2691 3764 1859 ...
  .. ..$ coffee_in_cafe        : num [1:781] 1.73 0.85 1.99 1.88 5 4 5.38 5 5 4.03 ...
  .. ..$ cost_beer             : num [1:781] 1.73 0.85 1.99 1.88 5 4 5.38 5 5 4.03 ...
  .. ..$ places_to_work        : num [1:781] 1 0.8 1 1 1 1 1 1 1 0.8 ...
  .. ..$ free_wifi_available   : num [1:781] 0.4 0.6 0.6 1 0.6 1 0.6 0.4 1 0.24 ...
  .. ..$ internet_speed        : int [1:781] 31 14 15 16 118 81 18 23 55 24 ...
  .. ..$ freedom_score         : num [1:781] 0.6 0.2 0.8 0.6 0.6 0.6 0.8 0.6 0.6 0.8 ...
  .. ..$ peace_score           : num [1:781] 0.8 0.4 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 ...
  .. ..$ safety                : num [1:781] 0.6 0.8 0.8 1 0.73 0.73 0.8 0.8 0.6 0.8 ...
  .. ..$ fragile_states_index  : num [1:781] 52.7 78.8 40.8 35.1 34 34 39.8 34 34 39.8 ...
  .. ..$ press_freedom_index   : num [1:781] 28.2 44.5 16.7 24.4 22.5 ...
  .. ..$ female_friendly       : num [1:781] 1 0.8 1 1 0.8 0.8 0.8 0.8 0.8 0.8 ...
  .. ..$ lgbt_friendly         : num [1:781] 0.27 0.6 0.6 0.8 0.6 1 1 0.8 0.8 1 ...
  .. ..$ friendly_to_foreigners: num [1:781] 0.6 0.6 0.8 0.8 0.8 0.8 0.8 1 1 0.8 ...
  .. ..$ racism                : num [1:781] 0.4 0.4 0.42 0 0.8 0.8 0.6 0.8 0.8 1 ...
  .. ..$ leisure               : num [1:781] 0.8 0.62 1 1 1 1 0.6 1 0.6 0.78 ...
  .. ..$ life_score            : num [1:781] 0.86 0.75 0.83 0.93 0.95 1 0.88 0.95 0.92 0.85 ...
  .. ..$ nightlife             : num [1:781] 1 0.4 1 0.6 1 1 0.8 1 1 0.8 ...
  .. ..$ weed                  : int [1:781] 0 0 1 0 0 0 1 1 1 1 ...
  ..$ row.w.init: num [1:781] 1 1 1 1 1 1 1 1 1 1 ...
  ..$ call      : language PCA(X = data[, vars], scale.unit = TRUE, graph = FALSE)
 - attr(*, "class")= chr [1:2] "PCA" "list "

Ok, lets see look at the “screeplot”, a diagnostic visualization that displays the variance explained by every component. We here use the factoextra package, like for all following visualizations with the fviz_ prefix. Notice that the output in every case is an ggplot2 object, which could be complemented with further layers.

As expected, we see that the first component already captures a main share of the variance. Let’s look at the corresponding eigenvalues.

For feature selection, our rule-of-thumb is to only include components with an eigenvalue > 1, meaning that we in this case would have reduced our data to 4 dimensions. Lets project them onto 2-dimensional space and take a look at the vector of our features.

We see that they tend to cluster in 3 groups:

  1. Fun, friendlyness, work (upper right)
  2. Costs (lower right)
  3. Safety and stability (left)

Lets look at the numeric values.

Principal Component Analysis Results for variables
 ===================================================
  Name       Description                                    
1 "$coord"   "Coordinates for the variables"                
2 "$cor"     "Correlations between variables and dimensions"
3 "$cos2"    "Cos2 for the variables"                       
4 "$contrib" "contributions of the variables"               

The results-object also contains the observations loading on the components.

Principal Component Analysis Results for variables
 ===================================================
  Name       Description                                    
1 "$coord"   "Coordinates for the variables"                
2 "$cor"     "Correlations between variables and dimensions"
3 "$cos2"    "Cos2 for the variables"                       
4 "$contrib" "contributions of the variables"               

Let’s visualize our observations and the variable-loading together in the space of the first 2 components.

We cal also briefly check if our ndimensionality reductions is helpful to differentiate between nomadscore.

Note: Vectorization and distances

Just a sidenote (which might become more important in later lectures): The components delivered by a PCA can also be used to create distance or similarity measures between two observations. You might need to refresh a bit of your vector algebra for the different ways to crate distance measures between vectors. Here, we just will use the simple “euclidian” distance in n-dimensional space. This can be done with the base-R dist() function. However, the FactoMineR has a function get_dist() which I prefer, since it includes a couple of other useful distance measurs.

The resulting distance object can be tansformed in a distance matrix. We will also add names for the matrix dimensions.

This matrix we could, for example, use to create a distance metwork (as we will do in M2). When continuing with “tidy” data, we would like to transform it in what we in network-jargon call a “edgelist”. So, that’s a classical use of the gather() function. However, since we have a matrix here, I will use the melt() function of the reshape2 package, since it automatically tidies the names of amtrix dimensions.Notice: This is a very convenient but not the most efficient way to create distance edgelists. In case we have a very large number of entities, you might want to learn how to deal with sarse-matrices. More on that again in M2.


Attaching package: <U+393C><U+3E31>reshape2<U+393C><U+3E32>

The following objects are masked from <U+393C><U+3E31>package:data.table<U+393C><U+3E32>:

    dcast, melt

The following object is masked from <U+393C><U+3E31>package:tidyr<U+393C><U+3E32>:

    smiths

Ok, lets just take a brief look which cities are most similar, and most distant in terms of their characteristics.

Sidenote: Here, we created the distance based on al components equally. Instead, one could weight the distance by the component’s variance explained, that the most explanatory component gets higher weights. That would be a nice exercise.

Such distance edgelists can be extremely informative. However, we will for not not use it anymore in the analysis to come, so lets get rid of the big objects.

Clustering

Introduction

Types of Clustering

Clustering can be broadly divided into two subgroups:

  1. Hard clustering: in hard clustering, each data object or point either belongs to a cluster completely or not. For example in the Uber dataset, each location belongs to either one borough or the other.
  2. Soft clustering: in soft clustering, a data point can belong to more than one cluster with some probability or likelihood value. For example, you could identify some locations as the border points belonging to two or more boroughs.

Clustering algorithms can also be categorized based on their cluster model, that is based on how they form clusters or groups. This tutorial only highlights some of the prominent clustering algorithms.

  • Connectivity-based clustering: the main idea behind this clustering is that data points that are closer in the data space are more related (similar) than to data points farther away. The clusters are formed by connecting data points according to their distance. At different distances, different clusters will form and can be represented using a dendrogram, which gives away why they are also commonly called hierarchical clustering. These methods do not produce a unique partitioning of the dataset, rather a hierarchy from which the user still needs to choose appropriate clusters by choosing the level where they want to cluster. Note: They are also not very robust towards outliers, which might show up as additional clusters or even cause other clusters to merge.

  • Centroid-based clustering: in this type of clustering, clusters are represented by a central vector or a centroid. This centroid might not necessarily be a member of the dataset. This is an iterative clustering algorithms in which the notion of similarity is derived by how close a data point is to the centroid of the cluster. k-means is a centroid based clustering, and will you see this topic more in detail later on in the tutorial.

  • Distribution-based clustering: this clustering is very closely related to statistics: distributional modeling. Clustering is based on the notion of how probable is it for a data point to belong to a certain distribution, such as the Gaussian distribution, for example. Data points in a cluster belong to the same distribution. These models have a strong theoritical foundation, however they often suffer from overfitting. Gaussian mixture models, using the expectation-maximization algorithm is a famous distribution based clustering method.

  • Density-based methods: search the data space for areas of varied density of data points. Clusters are defined as areas of higher density within the data space compared to other regions. Data points in the sparse areas are usually considered to be noise and/or border points. The drawback with these methods is that they expect some kind of density guide or parameters to detect cluster borders. DBSCAN and OPTICS are some prominent density based clustering.

So, what is the best to use? Hard to say. Clustering is an subjective task and there can be more than one correct clustering algorithm. Every algorithm follows a different set of rules for defining the ‘similarity’ among data points. The most appropriate clustering algorithm for a particular problem often needs to be chosen experimentally, unless there is a mathematical reason to prefer one clustering algorithm over another. An algorithm might work well on a particular dataset but fail for a different kind of dataset. Since there is most times no wrong or right, the clustering that delivers the most useful results is the way to go.

K-means Clustering

K-means clustering is the most commonly used unsupervised machine learning algorithm for dividing a given dataset into k clusters, which must be provided by the user. The basic idea behind k-means clustering consists of defining clusters so that the total intra-cluster variation (known as total within-cluster variation) is minimized. There are several k-means algorithms available. However, the standard algorithm defines the total within-cluster variation as the sum of squared distances Euclidean distances between items and the corresponding centroid. Its an iterative process containing the following steps

  1. Specify k - the number of clusters to be created.
  2. Select randomly k objects from the dataset as the initial cluster centers.
  3. Assign each observation to their closest centroid, based on the Euclidean distance between the object and the centroid.
  4. For each of the k clusters recompute the cluster centroid by calculating the new mean value of all the data points in the cluster.
  5. Iteratively minimize the total within sum of square. Repeat Step 3 and Step 4, until the centroids do not change or the maximum number of iterations is reached (R uses 10 as the default value for the maximum number of iterations).

So, lets do that. As already mentioned, we have to upfront choose our k. However, there exists some guidance, for example the highest gain in “total within sum of sqares” (fast to calculate), the “siluette”, as well as the “gap statistics” (hard to calculate, takes time).

Ok,w e here settle for 3 (executive desicion). Before we start, something weird upfront. The function takes the observation names from the rownames (which nobody uses anymore, and are depreciated by dplyr). So, remeber to define them just straight before you cluster, otherwise the next dplyr pipe will delete them again.

Oflets run the algorythm.

List of 9
 $ cluster     : Named int [1:781] 3 3 2 2 2 2 2 2 2 2 ...
  ..- attr(*, "names")= chr [1:781] "Budapest" "Chiang Mai" "Prague" "Taipei" ...
 $ centers     : num [1:3, 1:21] -0.671 0.69 -0.377 -0.415 0.417 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:3] "1" "2" "3"
  .. ..$ : chr [1:21] "cost_nomad" "cost_coworking" "cost_expat" "coffee_in_cafe" ...
 $ totss       : num 16380
 $ withinss    : num [1:3] 2475 4794 2592
 $ tot.withinss: num 9861
 $ betweenss   : num 6519
 $ size        : int [1:3] 237 341 203
 $ iter        : int 3
 $ ifault      : int 0
 - attr(*, "class")= chr "kmeans"

Again, lets visualize it. To have a meaningful way for 2d visualization, we again project the observations on the space of the first 2 components.

Ok, we got 3 clusters. Let’s look what’s in them.

Hirarchical Clustering

Introduction

The key operation in hierarchical agglomerative clustering is to repeatedly combine the two nearest clusters into a larger cluster. There are three key questions that need to be answered first:

  • How do you represent a cluster of more than one point?
  • How do you determine the “nearness” of clusters?
  • When do you stop combining clusters?
  • Hopefully by the end this tutorial you will be able to answer all of these questions. Before applying hierarchical clustering let’s have a look at its working:
  1. It starts by calculating the distance between every pair of observation points and store it in a distance matrix.
  2. It then puts every point in its own cluster.
  1. Then it starts merging the closest pairs of points based on the distances from the distance matrix and as a result the amount of clusters goes down by 1.
  1. Then it recomputes the distance between the new cluster and the old ones and stores them in a new distance matrix.
  2. Lastly it repeats steps 2 and 3 until all the clusters are merged into one single cluster.

There are several ways to measure the distance between clusters in order to decide the rules for clustering, and they are often called Linkage Methods. Some of the common linkage methods are:

  • Complete-linkage: calculates the maximum distance between clusters before merging.
  • Single-linkage: calculates the minimum distance between the clusters before merging. This linkage may be used to detect high values in your dataset which may be outliers as they will be merged at the end.
  • Average-linkage: calculates the average distance between clusters before merging.
  • Centroid-linkage: finds centroid of cluster 1 and centroid of cluster 2, and then calculates the distance between the two before merging (hint: usually not a good idea).

The choice of linkage method entirely depends on you and there is no hard and fast method that will always give you good results. Different linkage methods lead to different clusters.

Some further practical issues:

  • Data on different scales can cause undesirable resultsin clustering methods
  • Solution is to scale data so that features have same mean and standard deviation
  • Subtract mean of a feature from all observations, Divide each feature by the standard deviation ofthe feature
  • Normalized features have a mean of zero and a standard deviation of one

Performing a hirarchical clustering

However, let’s get it started and perform a cluster. We here use the hcut function, which includes most of the abovementioned mapproaches as options.

In hierarchical clustering, you categorize the objects into a hierarchy similar to a tree-like diagram which is called a dendrogram. The distance of split or merge (called height) is shown on the y-axis of the dendrogram below.

Notice how the dendrogram is built and every data point finally merges into a single cluster with the height(distance) shown on the y-axis.

Let’s inspect what’s in the clusters.

And again visualize them:

Looks very similar, even though the middle cluster is a bit more sqeezed in between now. We can also use our scatterplot diagnostics again, and color the observations by their cluster assignment.

Hirarchical Clustering based in PCA

You might already have wondered: “COuld one combine a PCA with clustering techniques”? The answer is: “Yes!”. In practice, that actually works very fine, and often delivers more robust clusters. So, lets give it a shot. We could do it by hand, but the HCPC function already does that for us, and offers also a nice diagnostic viz.

To finish up, lets plot it in a map, simplest way possible.

Note: Comparing with K-Means clustering algorithm

You might have heard about the k-means clustering algorithm; if not, take a look at this tutorial. There are many fundamental differences between the two algorithms, although any one can perform better than the other in different cases. Some of the differences are:

  • Distance used: Hierarchical clustering can virtually handle any distance metric while k-means rely on euclidean distances.
  • Stability of results: k-means requires a random step at its initialization that may yield different results if the process is re-run. That wouldn’t be the case in hierarchical clustering.
  • Number of Clusters: While you can use elbow plots, Silhouette plot etc. to figure the right number of clusters in k-means, hierarchical too can use all of those but with the added benefit of leveraging the dendrogram for the same.
  • Computation Complexity: K-means is less computationally expensive than hierarchical clustering and can be run on large datasets within a reasonable time frame, which is the main reason k-means is more popular.

Note: Measuring Godness-of-Fit in clusters

Perhaps the most important part in any unsupervised learning task is the analysis of the results. After you have performed the clustering using any algorithm and any sets of parameters you need to make sure that you did it right. But how do you determine that?

Well, there are many measures to do this, perhaps the most popular one is the Dunn’s Index. Dunn’s index is the ratio between the minimum inter-cluster distances to the maximum intra-cluster diameter. The diameter of a cluster is the distance between its two furthermost points. In order to have well separated and compact clusters you should aim for a higher Dunn’s index.

Furthermore, graphical inspection often helps comparing the results of different algorithms and poarameters. Here you find some advanced diagnostic visualizations for hirarchical clustering.

Lastly, a clusters quality is to a large extend determined by its usefulness. * Internal Validity * External Validity

Your turn!

So, why not have some fun on your own now? try to use what you learned up to now in the following extercise. —> HERE <— you will find a dataset on Gert Hofstede’s “6-D model of national culture”“. This popular measures of country-level culture in (by now) 6 dimensions became very popular in sociology, economics, and management science to explain cross-cultural interaction as well as frictions. a exhaustive documentation of the 2013 dataset can be found here. It contains the following variables.

Ok, looks interesting. Let’s do the fololwing:

  1. The data is not perfect. So some small upfront-munging is necessary.
  2. Gert Hofstede claims this dimensions to emasure orthogonal features of culture. That raises the question if they reasy measure different constructs. To find out, lets execute a PCA on them. How do the dimensions load? And how do countries score? Illustrate and visualize the results.
  3. Can we form meaningful “cultural clusters” among countries?
  4. Let’s create a meaningful measure for “cultural distance” between countries. What do we see? Interpret.
  5. (Advanced) Does bilateral “cultural distance” or the assignment to a “cultural cluster” help us to explain other interaction between countries we might be interested in, such as trade, migration etc.? Here you will need some skills from M1-1 & 2.

Have fun!

LS0tDQp0aXRsZTogJ00xLTc6IFVuc3VwZXJ2aXNlZCBNYWNoaW5lIExlYXJuaW5nJw0KYXV0aG9yOiAiRGFuaWVsIFMuIEhhaW4gKGRzaEBidXNpbmVzcy5hYXUuZGspIg0KZGF0ZTogIjEyLzA5LzIwMTgiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdG9jOiB5ZXMNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vDQogICAgdG9jOiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMjIyBHZW5lcmljIHByZWFtYmxlDQpTeXMuc2V0ZW52KExBTkcgPSAiZW4iKQ0KDQojIyMgQ2xlYW4gV29ya3NwYWNlIChJIGxpa2UgdG8gc3RhcnQgY2xlYW4pDQpybShsaXN0PWxzKCkpOyBncmFwaGljcy5vZmYoKSAjIGdldCByaWQgb2YgZXZlcnl0aGluZyBpbiB0aGUgd29ya3NwYWNlDQpkZXRhY2hBbGxQYWNrYWdlcyA8LSBmdW5jdGlvbigpIHsgIyBBbHNvLCBkZXRhY2ggcGFja2FnZXMgdG8gYXZvaWQgZnVuY3Rpb25zIG1hc2tlZCBieSBvdGhlcnMNCiAgYmFzaWMucGFja2FnZXMgPC0gYygicGFja2FnZTpzdGF0cyIsInBhY2thZ2U6Z3JhcGhpY3MiLCJwYWNrYWdlOmdyRGV2aWNlcyIsInBhY2thZ2U6dXRpbHMiLCJwYWNrYWdlOmRhdGFzZXRzIiwicGFja2FnZTptZXRob2RzIiwicGFja2FnZTpiYXNlIikNCiAgcGFja2FnZS5saXN0IDwtIHNlYXJjaCgpW2lmZWxzZSh1bmxpc3QoZ3JlZ2V4cHIoInBhY2thZ2U6IixzZWFyY2goKSkpPT0xLFRSVUUsRkFMU0UpXQ0KICBwYWNrYWdlLmxpc3QgPC0gc2V0ZGlmZihwYWNrYWdlLmxpc3QsYmFzaWMucGFja2FnZXMpDQogIGlmIChsZW5ndGgocGFja2FnZS5saXN0KT4wKSAgZm9yIChwYWNrYWdlIGluIHBhY2thZ2UubGlzdCkgZGV0YWNoKHBhY2thZ2UsIGNoYXJhY3Rlci5vbmx5PVRSVUUpDQp9DQpkZXRhY2hBbGxQYWNrYWdlcygpOyBybShkZXRhY2hBbGxQYWNrYWdlcykNCg0KIyMjIExvYWQgcGFja2FnZXMgIFN0YW5kYXJkDQpsaWJyYXJ5KGtuaXRyKSAjIEZvciBkaXNwbGF5IG9mIHRoZSBtYXJrZG93bg0KbGlicmFyeSh0aWR5dmVyc2UpICMgQ29sbGVjdGlvbiBvZiBhbGwgdGhlIGdvb2Qgc3R1ZmYgbGlrZSBkcGx5ciwgZ2dwbG90MiBlY3QuDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KbGlicmFyeShkYXRhLnRhYmxlKSAjIEdvb2QgZm9ybWF0IHRvIHdvcmsgd2l0aCBsYXJnZSBkYXRhc2V0cw0KbGlicmFyeShza2ltcikgIyBOaWNlIGRlc2NyaXB0aXZlcw0KDQojIyMgcGltcCB1cCBtZW1vcnkgKHRvIHNhdmUgb24gZGlzayBpZiBuZWNlc3NhcnksIG9ubHkgd29ya3Mgb24gd2luZG93cykNCiNtZW1vcnkubGltaXQoMTAgKiAxMF4xMCkNCg0KIyMjIEtuaXRyIG9wdGlvbnMNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFKQ0KYGBgDQoNClNvbWUgaG91c2VrZWVwaW5nOiBJbiBjYXNlIHlvdSBkb24ndCBoYXZlIHRoZSBuZWNlc3NhcnkgcGFja2FnZXMgaW5zdGFsbGVkLCBydW4gdGhpcyBzY3JpcHQgdG8gZG8gc28uDQoNCmBgYHtyfQ0KIyMjIEluc3RhbGwgcGFja2FnZXMgaWYgbmVjZXNzYXJ5DQpsaXN0Lm9mLnBhY2thZ2VzIDwtIGMoImRldnRvb2xzIiwgInJzdHVkaW9hcGkiLCAia25pdHIiLCAidGlkeXZlcnNlIiwgImRhdGEudGFibGUiLCAic2tpbXIiLCAgImZhY3RvZXh0cmEiLCAiRmFjdG9NaW5lUiIsICJHR2FsbHkiLCAiVklNIiwgIm1pY2UiLCAicmVzaGFwZTIiKQ0KbmV3LnBhY2thZ2VzIDwtIGxpc3Qub2YucGFja2FnZXNbIShsaXN0Lm9mLnBhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl0pXQ0KaWYobGVuZ3RoKG5ldy5wYWNrYWdlcykpIGluc3RhbGwucGFja2FnZXMobmV3LnBhY2thZ2VzKQ0Kcm0obGlzdC5vZi5wYWNrYWdlcykNCmBgYA0KDQoNCiMgTWFjaGluZSBMZWFybmluZzogU29tZSBjbGFyaWZpY2F0aW9ucw0KDQojIyBHZW5lcmFsDQoNCkFzIHdpdGggYW55IGNvbmNlcHQsIG1hY2hpbmUgbGVhcm5pbmcgbWF5IGhhdmUgYSBzbGlnaHRseSBkaWZmZXJlbnQgZGVmaW5pdGlvbiwgZGVwZW5kaW5nIG9uIHdob20geW91IGFzay4gQSBsaXR0bGUgY29tcGlsYXRpb24gb2YgZGVmaW5pdGlvbnMgYnkgYWNhZGVtaWNzIGFuZCBwcmFjdGlvbmVlcnMgYWxpa2U6DQoNCiogIk1hY2hpbmUgTGVhcm5pbmcgYXQgaXRzIG1vc3QgYmFzaWMgaXMgdGhlIHByYWN0aWNlIG9mIHVzaW5nIGFsZ29yaXRobXMgdG8gcGFyc2UgZGF0YSwgbGVhcm4gZnJvbSBpdCwgYW5kIHRoZW4gbWFrZSBhIGRldGVybWluYXRpb24gb3IgcHJlZGljdGlvbiBhYm91dCBzb21ldGhpbmcgaW4gdGhlIHdvcmxkLiIgLSBOdmlkaWEgDQoqICJNYWNoaW5lIGxlYXJuaW5nIGlzIHRoZSBzY2llbmNlIG9mIGdldHRpbmcgY29tcHV0ZXJzIHRvIGFjdCB3aXRob3V0IGJlaW5nIGV4cGxpY2l0bHkgcHJvZ3JhbW1lZC4iIC0gU3RhbmZvcmQNCiogIk1hY2hpbmUgbGVhcm5pbmcgaXMgYmFzZWQgb24gYWxnb3JpdGhtcyB0aGF0IGNhbiBsZWFybiBmcm9tIGRhdGEgd2l0aG91dCByZWx5aW5nIG9uIHJ1bGVzLWJhc2VkIHByb2dyYW1taW5nLiItIE1jS2luc2V5ICYgQ28uDQoqICJNYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgY2FuIGZpZ3VyZSBvdXQgaG93IHRvIHBlcmZvcm0gaW1wb3J0YW50IHRhc2tzIGJ5IGdlbmVyYWxpemluZyBmcm9tIGV4YW1wbGVzLiIgLSBVbml2ZXJzaXR5IG9mIFdhc2hpbmd0b24NCiogIlRoZSBmaWVsZCBvZiBNYWNoaW5lIExlYXJuaW5nIHNlZWtzIHRvIGFuc3dlciB0aGUgcXVlc3Rpb24gIkhvdyBjYW4gd2UgYnVpbGQgY29tcHV0ZXIgc3lzdGVtcyB0aGF0IGF1dG9tYXRpY2FsbHkgaW1wcm92ZSB3aXRoIGV4cGVyaWVuY2UsIGFuZCB3aGF0IGFyZSB0aGUgZnVuZGFtZW50YWwgbGF3cyB0aGF0IGdvdmVybiBhbGwgbGVhcm5pbmcgcHJvY2Vzc2VzPyIgLSBDYXJuZWdpZSBNZWxsb24gVW5pdmVyc2l0eQ0KDQoNCg0KIyMgU3VwZXJ2aXNlZCB2cy4gVW5zdXBlcnZpc2VkIE1MDQoNCiMjIyBBbiBpbnR1aXRpdmUgcGVyc3BlY3RpdmUNCg0KIVtdKG1lZGlhL203X3N1cGVyX3Vuc3VwZXIyLnBuZyl7d2lkdGg9NzUwcHh9DQoNCiMjIyBBIGZ1bmN0aW9uYWwgcGVyc3BlY3RpdmUNCg0KIVtdKG1lZGlhL203X3N1cGVyX3Vuc3VwZXIucG5nKXt3aWR0aD03NTBweH0NCg0KIyMjIE92ZXJ2aWV3IG92ZXIgdHlwZXMgYW5kIGNsYXNzZXMgb2YgYWxnb3JpdGhtcw0KDQpTb21ldGltZXMsIHRoZSBsaW5lcyBhcmUgYmx1cnJ5LCBnaXZlbiB0aGUgdmFzdCBhbW91bnQgb2YgYWxnb3JpdGhtcyBhbmQgdGVjaG5pcXVlcywgd2hpY2ggY29uc3RhbnRseSBleHBhbmRzLiANCg0KIVtdKG1lZGlhL203X21sX21hcC5wbmcpe3dpZHRoPTEwMDBweH0NCg0KIyMjIFN1cGVydmlzZWQgTUwNCg0KKiBDb25jZXJuZWQgd2l0aCBsYWJlbGluZy9jbGFzc2lmaWNhdGlvbi9pbnB1dC1vdXRwdXQtbWFwcGluZy9wcmVkaWN0aW9uIHRhc2tzDQoqIFN1YmplY3Qgb2YgdGhlIG5leHQgbGVjdHVyZSwgc28gc3RheSBwYXRpZW50DQoNCiMjIyBVbnN1cGVydmlzZWQgTUwNCg0KVGFza3MgcmVsYXRlZCB0byBwYXR0ZXJuIHJlY29nbml0aW9uIGFuZCBkYXRhIGV4cGxvcmF0aW9uLCBpbiBkYXNlIHRoZXJlIHlldCBkb2VzIG5vdCBleGlzdCBhIHJpZ2h0IGFuc3dlciBvciBwcm9ibGVtIHN0cnVjdHVyZS4gTWFpbiBhcHBsaWNhdGlvbg0KDQoxLiAqKkRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbjoqKiBGaW5kaW5nIHBhdHRlcm5zIGluIHRoZSBmZWF0dXJlcyBvZiB0aGUgZGF0YQ0KMi4gKipDbHVzdGVyaW5nOioqIEZpbmRpbmcgaG9tb2dlbm91cyBzdWJncm91cHMgd2l0aGluIGxhcmdlciBncm91cA0KDQojIERpbWVuc2lvbmFsaXR5IFJlZHVjdGlvbiBUZWNobmlxdWVzDQoNCiMjIEludHJvZHVjdGlvbg0KDQpEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlcyBhcmUgZm9yZW1vc3QgdXNlZnVsIHRvICh5b3UgbWlnaHQgc2VlIGl0IGNvbWluZykgcmVkdWNlIHRoZSBkaW1lbnNpb25hbGl0eSBvZiBvdXIgZGF0YS4gU28sIHdoYXQgZG9lcyB0aGF0IG1lYW4/IEFuZCB3aHkgc2hvdWxkIHdlIHdhbnQgdG8gZG8gdGhhdD8NCg0KRGltZW5zaW9ucyBoZXJlIGlzIGEgc3lub255bSBmb3IgdmFyaWFibGVzLCBzbyB3aGF0IHdlIHdhbnQgdG8gcmVhbGx5IGRvIGlzIGhhdmUgbGVzcyB2YXJpYWJsZXMuIFRvIGRvIHRoYXQsIHdlIGhhdmUgdG8gZmluZCB3YXlzIHRvIGV4cHJlc3MgdGhlIHNhbWUgYW1vdW50IG9mIGluZm9ybWF0aW9uIHdpdGggZmV3ZXIsIGJ1dCBtb3JlIGluZm9ybWF0aW9uIHJpY2ggdmFyaWFibGVzLiBUaGlzIGlzIHBhcnRpY3VsYXJseSB1c2VmdWwgdG86DQoNCiogRmluZCBwYXR0ZXJucyBpbiB0aGUgKipmZWF0dXJlcyoqIG9mIHRoZSBkYXRhDQoqIFZpc3VhbGl6YXRpb24gb2YgKipoaWdoLWRpbWVuc2lvbmFsKiogZGF0YQ0KKiAqKlByZS1wcm9jZXNzaW5nKiogYmVmb3JlIHN1cGVydmlzZWQgbGVhcm5pbmcNCg0KIyMjIE92ZXJ2aWV3IG92ZXIgVGVjaG5pcXVlcw0KDQpUaGUgdHlwZSBvZiBhbmFseXNpcyB0byBiZSBwZXJmb3JtZWQgZGVwZW5kcyBvbiB0aGUgZGF0YSBzZXQgZm9ybWF0cyBhbmQgc3RydWN0dXJlcy4gVGhlIG1vc3QgY29tbW9ubHkgdXNlZCBEUiB0ZWNobmlxdWVzIGFyZToNCg0KKiAqKlByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSk6KiogSXMgdXNlZCB0byBzdW1tYXJpemUgdGhlIGluZm9ybWF0aW9uIGNvbnRhaW5lZCBpbiBhIGNvbnRpbnVvdXMgKGkuZSwgcXVhbnRpdGF0aXZlKSBtdWx0aXZhcmlhdGUgZGF0YSBieSByZWR1Y2luZyB0aGUgZGltZW5zaW9uYWxpdHkgb2YgdGhlIGRhdGEgd2l0aG91dCBsb29zaW5nIGltcG9ydGFudCBpbmZvcm1hdGlvbi4NCiogKipDb3JyZXNwb25kZW5jZSBBbmFseXNpcyAoQ0EpOioqIEFuIGV4dGVuc2lvbiBvZiB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyBzdWl0ZWQgdG8gYW5hbHlzZSBhIGxhcmdlIGNvbnRpbmdlbmN5IHRhYmxlIGZvcm1lZCBieSB0d28gcXVhbGl0YXRpdmUgdmFyaWFibGVzIChvciBjYXRlZ29yaWNhbCBkYXRhKS4NCiogKipNdWx0aXBsZSBDb3JyZXNwb25kZW5jZSBBbmFseXNpcyAoTUNBKToqKiBBbiBhZGFwdGF0aW9uIG9mIENBIHRvIGEgZGF0YSB0YWJsZSBjb250YWluaW5nIG1vcmUgdGhhbiB0d28gY2F0ZWdvcmljYWwgdmFyaWFibGVzLg0KKiAqKk11bHRpcGxlIEZhY3RvciBBbmFseXNpcyAoTUZBKToqKiBEZWRpY2F0ZWQgdG8gZGF0YXNldHMgd2hlcmUgdmFyaWFibGVzIGFyZSBvcmdhbml6ZWQgaW50byBncm91cHMgKHF1YWxpdGF0aXZlIGFuZC9vciBxdWFudGl0YXRpdmUgdmFyaWFibGVzKS4NCiogKipIaWVyYXJjaGljYWwgTXVsdGlwbGUgRmFjdG9yIEFuYWx5c2lzIChITUZBKToqKiBBbiBleHRlbnNpb24gb2YgTUZBIGluIGEgc2l0dWF0aW9uIHdoZXJlIHRoZSBkYXRhIGFyZSBvcmdhbml6ZWQgaW50byBhIGhpZXJhcmNoaWNhbCBzdHJ1Y3R1cmUuDQoqICoqRmFjdG9yIEFuYWx5c2lzIG9mIE1peGVkIERhdGEgKEZBTUQpOioqIEEgcGFydGljdWxhciBjYXNlIG9mIHRoZSBNRkEsIGRlZGljYXRlZCB0byBhbmFseXplIGEgZGF0YSBzZXQgY29udGFpbmluZyBib3RoIHF1YW50aXRhdGl2ZSBhbmQgcXVhbGl0YXRpdmUgdmFyaWFibGVzLg0KDQohW10obWVkaWEvbTdfZGltX3RlY2gucG5nKXt3aWR0aD01MDBweH0NCg0KIyMjIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkNCg0KIyMjIyBHZW5lcmFsDQoNCiogQSBwb3B1bGFyIG1ldGhvZCBpcyBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIChQQ0EpDQoqIFRocmVlIGdvYWxzIHdoZW4gZmluZGluZyBsb3dlciBkaW1lbnNpb25hbA0KcmVwcmVzZW50YXRpb24gb2YgZmVhdHVyZXM6DQogICAgIDEuIEZpbmQgbGluZWFyIGNvbWJpbmF0aW9uIG9mIHZhcmlhYmxlcyB0byBjcmVhdGVwcmluY2lwYWwgY29tcG9uZW50cw0KICAgICAyLiBNYWludGFpbiBtb3N0IHZhcmlhbmNlIGluIHRoZSBkYXRhDQogICAgIDMuIFByaW5jaXBhbCBjb21wb25lbnRzIGFyZSB1bmNvcnJlbGF0ZWQgKGkuZS5vcnRob2dvbmFsIHRvIGVhY2ggb3RoZXIpDQoNCiMjIyMgVGhlIG1hdGggYW5kIGludHVpdGlvbiBiZWhpbmQgaXQNClRoZSBtYXRoZW1hdGljcyB1bmRlcmx5aW5nIGl0IGFyZSBzb21ld2hhdCBjb21wbGV4LCBzbyBJIHdvbid0IGdvIGludG8gdG9vIG11Y2ggZGV0YWlsLCBidXQgdGhlIGJhc2ljcyBvZiBQQ0EgYXJlIGFzIGZvbGxvd3M6IHlvdSB0YWtlIGEgZGF0YXNldCB3aXRoIG1hbnkgdmFyaWFibGVzLCBhbmQgeW91IHNpbXBsaWZ5IHRoYXQgZGF0YXNldCBieSB0dXJuaW5nIHlvdXIgb3JpZ2luYWwgdmFyaWFibGVzIGludG8gYSBzbWFsbGVyIG51bWJlciBvZiAiUHJpbmNpcGFsIENvbXBvbmVudHMiLg0KDQohW10obWVkaWEvbTdfUENBMS5wbmcpe3dpZHRoPTUwMHB4fQ0KDQpCdXQgd2hhdCBhcmUgdGhlc2UgZXhhY3RseT8gUHJpbmNpcGFsIENvbXBvbmVudHMgYXJlIHRoZSB1bmRlcmx5aW5nIHN0cnVjdHVyZSBpbiB0aGUgZGF0YS4gVGhleSBhcmUgdGhlIGRpcmVjdGlvbnMgd2hlcmUgdGhlcmUgaXMgdGhlIG1vc3QgdmFyaWFuY2UsIHRoZSBkaXJlY3Rpb25zIHdoZXJlIHRoZSBkYXRhIGlzIG1vc3Qgc3ByZWFkIG91dC4gVGhpcyBtZWFucyB0aGF0IHdlIHRyeSB0byBmaW5kIHRoZSBzdHJhaWdodCBsaW5lIHRoYXQgYmVzdCBzcHJlYWRzIHRoZSBkYXRhIG91dCB3aGVuIGl0IGlzIHByb2plY3RlZCBhbG9uZyBpdC4gVGhpcyBpcyB0aGUgZmlyc3QgcHJpbmNpcGFsIGNvbXBvbmVudCwgdGhlIHN0cmFpZ2h0IGxpbmUgdGhhdCBzaG93cyB0aGUgbW9zdCBzdWJzdGFudGlhbCB2YXJpYW5jZSBpbiB0aGUgZGF0YS4NCg0KIVtdKG1lZGlhL203X1BDQTIucG5nKXt3aWR0aD01MDBweH0NCg0KV2hlcmUgbWFueSB2YXJpYWJsZXMgY29ycmVsYXRlIHdpdGggb25lIGFub3RoZXIsIHRoZXkgd2lsbCBhbGwgY29udHJpYnV0ZSBzdHJvbmdseSB0byB0aGUgc2FtZSBwcmluY2lwYWwgY29tcG9uZW50LiBFYWNoIHByaW5jaXBhbCBjb21wb25lbnQgc3VtcyB1cCBhIGNlcnRhaW4gcGVyY2VudGFnZSBvZiB0aGUgdG90YWwgdmFyaWF0aW9uIGluIHRoZSBkYXRhc2V0LiBXaGVyZSB5b3VyIGluaXRpYWwgdmFyaWFibGVzIGFyZSBzdHJvbmdseSBjb3JyZWxhdGVkIHdpdGggb25lIGFub3RoZXIsIHlvdSB3aWxsIGJlIGFibGUgdG8gYXBwcm94aW1hdGUgbW9zdCBvZiB0aGUgY29tcGxleGl0eSBpbiB5b3VyIGRhdGFzZXQgd2l0aCBqdXN0IGEgZmV3IHByaW5jaXBhbCBjb21wb25lbnRzLiBVc3VhbGx5LCB0aGUgZmlyc3QgcHJpbmNpcGFsIGNvbXBvbmVudCBjYXB0dXJlcyB0aGUgbWFpbiBzaW1pbGFyaXR5IGluIHlvdXIgZGF0YSwgdGhlIHNlY29uZCB0aGUgbWFpbiBkaWZmZXJlbmNlLg0KDQohW10obWVkaWEvbTdfUENBMy5wbmcpe3dpZHRoPTUwMHB4fQ0KDQpUaGVzZSBwcmluY2lwYWwgY29tcG9uZW50cyBjYW4gYmUgY29tcHV0ZWQgdmlhICoqRWlnZW52YWx1ZXMqKiBhbmQgKipFaWdlbnZlY3RvcnMqKi4gSnVzdCBsaWtlIG1hbnkgdGhpbmdzIGluIGxpZmUsIGVpZ2VudmVjdG9ycywgYW5kIGVpZ2VudmFsdWVzIGNvbWUgaW4gcGFpcnM6IGV2ZXJ5IGVpZ2VudmVjdG9yIGhhcyBhIGNvcnJlc3BvbmRpbmcgZWlnZW52YWx1ZS4gU2ltcGx5IHB1dCwgYW4gZWlnZW52ZWN0b3IgaXMgYSBkaXJlY3Rpb24sIHN1Y2ggYXMgInZlcnRpY2FsIiBvciAiNDUgZGVncmVlcyIsIHdoaWxlIGFuIGVpZ2VudmFsdWUgaXMgYSBudW1iZXIgdGVsbGluZyB5b3UgaG93IG11Y2ggdmFyaWFuY2UgdGhlcmUgaXMgaW4gdGhlIGRhdGEgaW4gdGhhdCBkaXJlY3Rpb24uIFRoZSBlaWdlbnZlY3RvciB3aXRoIHRoZSBoaWdoZXN0IGVpZ2VudmFsdWUgaXMsIHRoZXJlZm9yZSwgdGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQuIFRoZSBudW1iZXIgb2YgZWlnZW52YWx1ZXMgYW5kIGVpZ2VudmVjdG9ycyB0aGF0IGV4aXRzIGlzIGVxdWFsIHRvIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB0aGUgZGF0YSBzZXQgaGFzLiBDb25zZXF1ZW50bHksIHdlIGNhbiByZWZyYW1lIGEgZGF0YXNldCBpbiB0ZXJtcyBvZiB0aGVzZSBlaWdlbnZlY3RvcnMgYW5kIGVpZ2VudmFsdWVzIHdpdGhvdXQgY2hhbmdpbmcgdGhlIHVuZGVybHlpbmcgaW5mb3JtYXRpb24uIA0KDQpOb3RlIHRoYXQgcmVmcmFtaW5nIGEgZGF0YXNldCByZWdhcmRpbmcgYSBzZXQgb2YgZWlnZW52YWx1ZXMgYW5kIGVpZ2VudmVjdG9ycyBkb2VzIG5vdCBlbnRhaWwgY2hhbmdpbmcgdGhlIGRhdGEgaXRzZWxmLCB5b3UncmUganVzdCBsb29raW5nIGF0IGl0IGZyb20gYSBkaWZmZXJlbnQgYW5nbGUsIHdoaWNoIHNob3VsZCByZXByZXNlbnQgdGhlIGRhdGEgYmV0dGVyLg0KDQojIyBDYXNlIHN0dWR5OiAoRGlnaXRhbCkgTm9tYWQgTGlmZQ0KDQojIyMgKipPKiogJiAqKlMqKiAmICoqRSoqOiBMb2FkLCBjbGVhbiBhbmQgaW5zcGVjdA0KDQpBbGxyaWdodCwgbGV0cyBsb2FkIHNvbWUgZGF0YS4gSGVyZSwgd2Ugd2lsbCBkcmF3IGZyb20gc29tZSBvd24gd29yaywgd2hlcmUgd2UgZXhwbG9yZSB0aGUgbGlmZSBvZiBkaWdpdGFsIG5vbWFkcy4gVGhlIHBhcGVyIGlzIG5vdCB3cml0dGVuLCBidXQgdGhlIHByZWxpbWluYXJ5IHdvcmsgaXMgc3VtbWFyaXplZCBpbiBbdGhpcyBwcmVzZW50YXRpb25dKGh0dHBzOi8vYWF1ZGstbXkuc2hhcmVwb2ludC5jb20vOmI6L2cvcGVyc29uYWwvZHNoX2lkX2FhdV9kay9FU2V1dnBsRXl0WkN1TkJoS0dtQTRVOEJPR3BmYkdJYmlscVRHZGdRTEE0YTZBP2U9VUdSbnZSKS4gWW91IHByb2JhYmx5IGFscmVhZHkga25vdyB0aGUgZGF0YSBmcm9tIFtOb21hZExpc3RdKGh0dHBzOi8vbm9tYWRsaXN0LmNvbS8pLiBIZXJlLCB3ZSBsb29rIGF0IHRoZSAyMDE3IGNyYXdsIG9mIGNpdHkgZGF0YSwgd2hpY2ggY29tcGlsZXMgdGhlIGRpZ2l0YWwgbm9tYWRzIHJhbmtpbmcgb2YgY2l0aWVzIGFjY29yZGluZyB0byBhIGNvdXBsZSBvZiBkaW1lbnNpb25zLiBMZXRzIHRha2UgYSBsb29rLg0KDQpSb21hbidzIHdlYi1jcmF3bGQgYXRhIGlzIGFsd2F5cyBhIGJpdCBtZXNzeSwgc28gd2UgZG8gc29tZSBsaXR0bGUgY29zbWV0aWNzIHVwZnJvbnQuDQoNCg0KYGBge3J9DQpkYXRhIDwtIGZyZWFkKCJkYXRhL25vbWFkX2NpdGllcy5jc3YiLA0KICAgICAgICAgICAgICBzZXA9Ilx0IiwNCiAgICAgICAgICAgICAgZGVjPSIuIiwNCiAgICAgICAgICAgICAgbmEuc3RyaW5ncz1jKCJOQSIsICJEb3RNYXAoX19uZXh0X189RG90TWFwKCkpIiwgIkRvdE1hcCgpIikgKQ0KDQpkYXRhICU8PiUNCiAgc2VsZWN0KC1WMSwgLW5vbWFkU2NvcmUpICU+JQ0KICBzZWxlY3QocGxhY2UsIG5vbWFkX3Njb3JlLCBsb25naXR1ZGUsIGxhdGl0dWRlLCBldmVyeXRoaW5nKCkpICU+JQ0KICBtdXRhdGUocmFjaXNtID0gaWZfZWxzZShyYWNpc20gPiAxLCAxLCByYWNpc20pKQ0KYGBgDQoNCkxldHMgdGFrZSBhIGxvb2s6DQoNCmBgYHtyLCByZXN1bHRzPSJhc2lzIn0NCmhlYWQoZGF0YSkNCmdsaW1wc2UoZGF0YSkNCnNraW0oZGF0YSkNCmBgYA0KDQpRdWl0ZSBhIHNldCBvZiBpbnRlcmVzdGluZyBmZWF0dXJlcywgd2hpY2ggYXJlIGFsbCBudW1lcmljYWxseSBjb2RlZC4gTGV0cyBzZWxlY3QgdGhlIG9uZSB3ZSB3YW50IHRvIGFuYWx5emUgYW5kIG9yZ2FuaXplIHRoZW0gYSBiaXQuIFNpbmNlIGl0J3MgYSBsb3Qgb2YgdmFyaWFibGVzLCBJIGFmdGVyd2FyZHMgc2VsZWN0IG9ubHkgYSBzdWJzZXQgb24gd2hpY2ggd2UgZG8gc29tZSBncmFwaGljYWwgZXhwbG9yYXRpb24uDQoNCmBgYHtyfQ0KIyBWYXJpYWJsZXMgZm9yIGFuYWx5c2lzDQp2YXJzIDwtIGMoImNvc3Rfbm9tYWQiLCAiY29zdF9jb3dvcmtpbmciLCAiY29zdF9leHBhdCIsICJjb2ZmZWVfaW5fY2FmZSIsICJjb3N0X2JlZXIiLCAjIGNvc3RzDQogICAgICAgICAgInBsYWNlc190b193b3JrIiwgImZyZWVfd2lmaV9hdmFpbGFibGUiLCAiaW50ZXJuZXRfc3BlZWQiLCAjIHdvcmsNCiAgICAgICAgICAiZnJlZWRvbV9zY29yZSIsICJwZWFjZV9zY29yZSIsICJzYWZldHkiLCAiZnJhZ2lsZV9zdGF0ZXNfaW5kZXgiLCAicHJlc3NfZnJlZWRvbV9pbmRleCIsICMgc2FmZXR5ICYgZnJlZWRvbQ0KICAgICAgICAgICJmZW1hbGVfZnJpZW5kbHkiLCAibGdidF9mcmllbmRseSIsICJmcmllbmRseV90b19mb3JlaWduZXJzIiwgInJhY2lzbSIsICMgZnJpZW5kbHkNCiAgICAgICAgICAibGVpc3VyZSIsImxpZmVfc2NvcmUiLCJuaWdodGxpZmUiLCJ3ZWVkIiAjIGZ1biANCiAgICAgICAgICApDQoNCiMgVmFyaWFibGVzIGZvciBkZXNjcmlwdGl2ZXMNCnZhcnMuZGVzYyA8LSBjKCJub21hZF9zY29yZSIsICJjb3N0X25vbWFkIiwgInBsYWNlc190b193b3JrIiwgImZyZWVkb21fc2NvcmUiLCAiZnJpZW5kbHlfdG9fZm9yZWlnbmVycyIsICJsaWZlX3Njb3JlIikNCg0KYGBgDQoNCg0KT2ssIHRpbWUgZm9yIHNvbWUgZXhwbG9yYXRpb24uIEhlcmUgSSB3aWxsIGludHJvZHVjZSB0aGUgYEdHYWxseWAgcGFja2FnZSwgYSB3cmFwcGVyIGZvciBgZ2dwbG90MmAgd2hpY2ggaGFzIHNvbWUgZnVuY3Rpb25zIGZvciB2ZXJ5IG5pY2UgdmlzdWFsIHN1bW1hcmllcyBpbiBtYXRyaXggZm9ybS4NCg0KYGBge3Isd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFfQ0KbGlicmFyeShHR2FsbHkpDQpgYGANCg0KRmlyc3QsIGxldHMgbG9vayBhdCBhIGNsYXNzaWNhbCBjb3JyZWxhdGlvbiBtYXRyaXguDQoNCmBgYHtyLGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30NCmdnY29ycihkYXRhW3ZhcnNdLCBsYWJlbCA9IFRSVUUsIGxhYmVsX3NpemUgPSAzLCBsYWJlbF9yb3VuZCA9IDIsIGxhYmVsX2FscGhhID0gVFJVRSkNCmBgYA0KDQpFdmVuIGNvb2xlciwgdGhlIGBnZ3BhaXJzYCBmdW5jdGlvbiBjcmVhdGVzIHlvdSBhIHNjYXR0ZXJwbG90IG1hdHJpeCBwbHVzIGFsbCB2YXJpYWJsZSBkaXN0cmlidXRpb25zIGFuZCBjb3JyZWxhdGlvbnMuIEJlZm9yZSBJIHVzZWQgdGhlIHBhY2thZ2UgYFBlcmZvcm1hbmNlQW5hbHl0aWNzYCBmb3IgdGhhdCwgYnV0IEkgbGlrZSB0aGUgZ2dwbG90LXN0eWxlIG1vcmUuDQoNCmBgYHtyZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MTAsZmlnLmFsaWduPSdjZW50ZXInfQ0KZ2dwYWlycyhkYXRhWyx2YXJzLmRlc2NdLCANCiAgICAgICAgYWVzKGFscGhhID0gMC4zKSwgDQogICAgICAgIGdndGhlbWUgPSB0aGVtZV9ncmF5KCkpICANCmBgYA0KDQoNCiMjIyMgRGlncmVzc2lvbjogTWlzc2luZyB2YWx1ZSBpbXB1dGF0aW9uDQpUbyByZW1pbmQgeW91LCBjb21wb25lbnQgc2NvcmVzIGNhbm5vdCBiZSBjb21wdXRlZCBvbiBtaXNzaW5nIGZlYXR1cmVzLiBTbyBsZXRzIGltcHV0ZSB0aGVtLiBJdCdzIGEgZ29vZCBwb2ludCB0byBpbnRyb2R1Y2UgeW91IHRvIHNvbWUgbmVhdGggaW1wdXRhdGlvbiB0ZWNobmlxdWVzLiBGaXJzdCwgdGhlIHBhY2thZ2UgYFZJTWAgaGFzIHNvbWUgbmljZSBpbXB1dGF0aW9uIGZ1bmN0aW9ucywgYnV0IGFsc28gc29tZSBuaWNlIGRpYWduaXN0aWMgcGxvdHMuDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0UsZWNobz1GQUxTRX0NCmxpYnJhcnkoVklNKQ0KYGBgDQoNCg0KYGBge3JmaWcud2lkdGg9MTAsZmlnLmhlaWdodD0xMCxmaWcuYWxpZ249J2NlbnRlcid9DQptYXJnaW5tYXRyaXgoZGF0YVssdmFycy5kZXNjXSkgIyBOb3RlOiBDb29sIGZlYXR1cmUsIGJ1dCBtYXRyaXggYmVjb25lcyBqdXN0IHRvbyBiaWcgZm9yIG1hbnkgZmVhdHVyZXMNCg0KYWdncihkYXRhWyx2YXJzXSwgDQogICAgIGNvbCA9IGMoJ25hdnlibHVlJywncmVkJyksIA0KICAgICBudW1iZXJzID0gVFJVRSwgDQogICAgIHNvcnRWYXJzID0gVFJVRSwgDQogICAgIGxhYmVscyA9IG5hbWVzKGRhdGFbLHZhcnNdKSwgDQogICAgIGNleC5heGlzID0gMC41LCANCiAgICAgZ2FwID0gMC41LCANCiAgICAgeWxhYj1jKCJIaXN0b2dyYW0gb2YgbWlzc2luZyBkYXRhIiwiUGF0dGVybiIpKQ0KYGBgDQoNCkZvciB0aGUgcmVhbCBpbXB1dGF0aW9uLCBJIHByZWZlciB0aGUgYG1pY2VgIHBhY2thZ2UsIHdoaWNoIHdvcmtzIHdpdGggYSBuZXVyYWwgbmV0d29yayAidW5kZXIgdGhlIGhvb2QiLiBIZXJlLCBldmVyeSBmZWF0dXJlIGlzIHNlcXVlbnRpYWxseSBwcmVkaWN0ZWQgYnkgYWxsIG90aGVyIGV4aXN0aW5nIGZlYXR1cmVzIGluIGFuIGl0ZXJhdGl2ZSBwcm9jZXNzLiBTaW5jZSB0aGlzIHByb2Nlc3MgaW52b2x2ZXMgc29tZSBzdG9jaGFjaGljcywgSSBkZWZpbmUgYSBzZWVkIHVwZnJvbnQgZm9yIHJlcHJvZHVjaWJsZSByZXN1bHRzLg0KDQoNCmBgYHtyLGVjaG89RkFMU0Usd2FybmluZz1GQUxTRX0NCnNldC5zZWVkKDEzMzcpDQoNCmxpYnJhcnkobWljZSkNCmRhdGEubWljZSA8LSBtaWNlKGRhdGEgPSBkYXRhWyx2YXJzXSwgbSA9IDEsIG1heGl0ID0gMTAwLCBzZWVkID0gMTMzNykgDQoNCmBgYA0KDQpMZXQncyBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGltcHV0ZWQgdnMuIHRoZSBleGlzdGluZyBmZWF0dXJlcy4NCg0KYGBge3IsZmlnLmFsaWduPSdjZW50ZXInfQ0KZGVuc2l0eXBsb3QoZGF0YS5taWNlKQ0KYGBgDQoNCkkgd291bGQgc2F5LCBnb29kIGVub3VnaC4gTGV0cyB0YWtlIHRoZW0hDQoNCmBgYHtyfQ0KdmFyLmltcCA8LSBjKCJmcmVlZG9tX3Njb3JlIiwgInBlYWNlX3Njb3JlIiwgImZyYWdpbGVfc3RhdGVzX2luZGV4IiwgInByZXNzX2ZyZWVkb21faW5kZXgiKQ0KDQpkYXRhLmltcCA8LSBjb21wbGV0ZShkYXRhLm1pY2UsIGFjdGlvbiA9IDEpDQpkYXRhWyx2YXIuaW1wXSA8LSBkYXRhLmltcFssdmFyLmltcF0NCnJtKGRhdGEubWljZSwgZGF0YS5pbXAsIHZhci5pbXApDQpgYGANCg0KDQoNCiMjIyBEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24NCg0KVG8gZXhlY3V0ZSB0aGUgUENBLCB3ZSdsbCBoZXJlIHVzZSB0aGUgW2BGYWN0b01pbmVSYF0oaHR0cDovL2ZhY3RvbWluZXIuZnJlZS5mci8pIHBhY2thZ2UgdG8gY29tcHV0ZSBQQ0EsIGFuZCAgW2BmYWN0b2V4dHJhYF0oaHR0cHM6Ly9naXRodWIuY29tL2thc3NhbWJhcmEvZmFjdG9leHRyYSkgZm9yIGV4dHJhY3RpbmcgYW5kIHZpc3VhbGl6aW5nIHRoZSByZXN1bHRzLiBgRmFjdG9NaW5lUmAgaXMgYSBncmVhdCBhbmQgbXkgZmF2b3JpdGUgcGFja2FnZSBmb3IgY29tcHV0aW5nIHByaW5jaXBhbCBjb21wb25lbnQgbWV0aG9kcyBpbiBSLiBJdCdzIHZlcnkgZWFzeSB0byB1c2UgYW5kIHZlcnkgd2VsbCBkb2N1bWVudGVkLiBUaGVyZSBhcmUgb3RoZXIgYWx0ZXJuYXRpdmVzIGFyb3VuZCwgYnV0IEkgc2luY2UgcXVpdGUgc29tZSB0aW1lIGZpbmQgaXQgdG8gYmUgdGhlIG1vc3QgcG93ZXJmdWwgYW5kIGNvbnZlbmllbnQgb25lLiBgZmFjdG9leHRyYWAgaXMganVzdCBhIGNvbnZlbmllbnQgYGdncGxvdGAgd3JhcHBlciB0aGF0IGVhc2lseSBwcm9kdWNlcyBuaWNlIGFuZCBpbmZvcm1hdGl2ZSBkaWFnbmlzdGljIHBsb3RzIGZvciBhIHZhcmlldHkgb2YgRFIgYW5kIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcy4NCg0KYGBge3Isd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFfQ0KbGlicmFyeShGYWN0b01pbmVSKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KYGBgDQoNCkxldHMgZG8gdGhhdC4gTm90aWNlIHRoZSBgc2NhbGUudW5pdCA9IFRSVUVgIGFyZ3VtZW50LCB3aGljaCB5b3Ugc2hvdWxkIEFMV0FZUyB1c2UuIEFmdGVyd2FyZHMsIHdlIHRha2UgYSBsb29rIGF0IHRoZSByZXN1bHRpbmcgbGlzdCBvYmplY3QuDQoNCmBgYHtyfQ0KcmVzLnBjYSA8LSBQQ0EoZGF0YVssdmFyc10sIHNjYWxlLnVuaXQgPSBUUlVFLCBncmFwaCA9IEZBTFNFKQ0KZ2xpbXBzZShyZXMucGNhKQ0KYGBgDQoNCk9rLCBsZXRzIHNlZSBsb29rIGF0IHRoZSAic2NyZWVwbG90IiwgYSBkaWFnbm9zdGljIHZpc3VhbGl6YXRpb24gdGhhdCBkaXNwbGF5cyB0aGUgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGV2ZXJ5IGNvbXBvbmVudC4gV2UgaGVyZSB1c2UgdGhlIGBmYWN0b2V4dHJhYCBwYWNrYWdlLCBsaWtlIGZvciBhbGwgZm9sbG93aW5nIHZpc3VhbGl6YXRpb25zIHdpdGggdGhlIGBmdml6X2AgcHJlZml4LiBOb3RpY2UgdGhhdCB0aGUgb3V0cHV0IGluIGV2ZXJ5IGNhc2UgaXMgYW4gYGdncGxvdDJgIG9iamVjdCwgd2hpY2ggY291bGQgYmUgY29tcGxlbWVudGVkIHdpdGggZnVydGhlciBsYXllcnMuDQoNCmBgYHtyLGZpZy5hbGlnbj0nY2VudGVyJ30NCmZ2aXpfc2NyZWVwbG90KHJlcy5wY2EsIA0KICAgICAgICAgICAgICAgYWRkbGFiZWxzID0gVFJVRSwgDQogICAgICAgICAgICAgICBuY3AgPSAxMCwgDQogICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKQ0KYGBgDQoNCkFzIGV4cGVjdGVkLCB3ZSBzZWUgdGhhdCB0aGUgZmlyc3QgY29tcG9uZW50IGFscmVhZHkgY2FwdHVyZXMgYSBtYWluIHNoYXJlIG9mIHRoZSB2YXJpYW5jZS4gTGV0J3MgbG9vayBhdCB0aGUgY29ycmVzcG9uZGluZyBlaWdlbnZhbHVlcy4NCg0KYGBge3J9DQphc190aWJibGUocmVzLnBjYSRlaWcpDQpgYGANCg0KRm9yIGZlYXR1cmUgc2VsZWN0aW9uLCBvdXIgcnVsZS1vZi10aHVtYiBpcyB0byBvbmx5IGluY2x1ZGUgY29tcG9uZW50cyB3aXRoIGFuIGVpZ2VudmFsdWUgPiAxLCBtZWFuaW5nIHRoYXQgd2UgaW4gdGhpcyBjYXNlIHdvdWxkIGhhdmUgcmVkdWNlZCBvdXIgZGF0YSB0byA0IGRpbWVuc2lvbnMuIExldHMgcHJvamVjdCB0aGVtIG9udG8gMi1kaW1lbnNpb25hbCBzcGFjZSBhbmQgdGFrZSBhIGxvb2sgYXQgdGhlIHZlY3RvciBvZiBvdXIgZmVhdHVyZXMuDQoNCg0KYGBge3IsZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MTAsZmlnLmFsaWduPSdjZW50ZXInfQ0KZnZpel9wY2FfdmFyKHJlcy5wY2EsIA0KICAgICAgICAgICAgIGFscGhhLnZhciA9ICJjb3MyIiwNCiAgICAgICAgICAgICBjb2wudmFyID0gImNvbnRyaWIiLA0KICAgICAgICAgICAgIGdyYWRpZW50LmNvbHMgPSBjKCIjMDBBRkJCIiwgIiNFN0I4MDAiLCAiI0ZDNEUwNyIpLA0KICAgICAgICAgICAgIHJlcGVsID0gVFJVRSwNCiAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKSANCmBgYA0KDQpXZSBzZWUgdGhhdCB0aGV5IHRlbmQgdG8gY2x1c3RlciBpbiAzIGdyb3VwczoNCg0KMS4gRnVuLCBmcmllbmRseW5lc3MsIHdvcmsgKHVwcGVyIHJpZ2h0KQ0KMi4gQ29zdHMgKGxvd2VyIHJpZ2h0KQ0KMy4gU2FmZXR5IGFuZCBzdGFiaWxpdHkgKGxlZnQpDQoNCkxldHMgbG9vayBhdCB0aGUgbnVtZXJpYyB2YWx1ZXMuDQoNCmBgYHtyfQ0KZ2V0X3BjYV92YXIocmVzLnBjYSkNCmFzX2RhdGFfZnJhbWUocmVzLnBjYSR2YXIkY29vcmQpDQpgYGANCg0KVGhlIHJlc3VsdHMtb2JqZWN0IGFsc28gY29udGFpbnMgdGhlIG9ic2VydmF0aW9ucyBsb2FkaW5nIG9uIHRoZSBjb21wb25lbnRzLg0KDQpgYGB7cn0NCmdldF9wY2FfdmFyKHJlcy5wY2EpDQphc19kYXRhX2ZyYW1lKGhlYWQocmVzLnBjYSRpbmQkY29vcmQpKQ0KYGBgDQoNCkxldCdzIHZpc3VhbGl6ZSBvdXIgb2JzZXJ2YXRpb25zIGFuZCB0aGUgdmFyaWFibGUtbG9hZGluZyB0b2dldGhlciBpbiB0aGUgc3BhY2Ugb2YgdGhlIGZpcnN0IDIgY29tcG9uZW50cy4NCg0KYGBge3IsLGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30NCmZ2aXpfcGNhX2JpcGxvdChyZXMucGNhLA0KICAgICAgICAgICAgICAgIGFscGhhLmluZCA9ICJjb3MyIiwNCiAgICAgICAgICAgICAgICBjb2wuaW5kID0gImNvbnRyaWIiLA0KICAgICAgICAgICAgICAgIGdyYWRpZW50LmNvbHMgPSBjKCIjMDBBRkJCIiwgIiNFN0I4MDAiLCAiI0ZDNEUwNyIpLA0KICAgICAgICAgICAgICAgIGdlb20gPSAicG9pbnQiLCANCiAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKSANCmBgYA0KDQoNCldlIGNhbCBhbHNvIGJyaWVmbHkgY2hlY2sgaWYgb3VyIG5kaW1lbnNpb25hbGl0eSByZWR1Y3Rpb25zIGlzIGhlbHBmdWwgdG8gZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIGBub21hZHNjb3JlYC4NCg0KYGBge3IsLGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30NCmRhdGEgJTw+JQ0KICBtdXRhdGUocG9wdWxhciA9IGlmZWxzZShub21hZF9zY29yZSA+IG1lYW4obm9tYWRfc2NvcmUpLCBUUlVFLCBGQUxTRSkpDQoNCmZ2aXpfcGNhX2JpcGxvdChyZXMucGNhLA0KICAgICAgICAgICAgICAgIGFscGhhLmluZCA9ICJjb3MyIiwNCiAgICAgICAgICAgICAgICBnZW9tID0gInBvaW50IiwgDQogICAgICAgICAgICAgICAgaGFiaWxsYWdlID0gZmFjdG9yKGRhdGEkcG9wdWxhciksIA0KICAgICAgICAgICAgICAgIGFkZEVsbGlwc2VzID0gVFJVRSwNCiAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKSANCmBgYA0KDQoNCiMjIyBOb3RlOiBWZWN0b3JpemF0aW9uIGFuZCBkaXN0YW5jZXMNCg0KSnVzdCBhIHNpZGVub3RlICh3aGljaCBtaWdodCBiZWNvbWUgbW9yZSBpbXBvcnRhbnQgaW4gbGF0ZXIgbGVjdHVyZXMpOiBUaGUgY29tcG9uZW50cyBkZWxpdmVyZWQgYnkgYSBQQ0EgY2FuIGFsc28gYmUgdXNlZCB0byBjcmVhdGUgZGlzdGFuY2Ugb3Igc2ltaWxhcml0eSBtZWFzdXJlcyBiZXR3ZWVuIHR3byBvYnNlcnZhdGlvbnMuIFlvdSBtaWdodCBuZWVkIHRvIHJlZnJlc2ggYSBiaXQgb2YgeW91ciB2ZWN0b3IgYWxnZWJyYSBmb3IgdGhlIGRpZmZlcmVudCB3YXlzIHRvIGNyYXRlIGRpc3RhbmNlIG1lYXN1cmVzIGJldHdlZW4gdmVjdG9ycy4gSGVyZSwgd2UganVzdCB3aWxsIHVzZSB0aGUgc2ltcGxlICJldWNsaWRpYW4iIGRpc3RhbmNlIGluIG4tZGltZW5zaW9uYWwgc3BhY2UuIFRoaXMgY2FuIGJlIGRvbmUgd2l0aCB0aGUgYmFzZS1SIGBkaXN0KClgIGZ1bmN0aW9uLiBIb3dldmVyLCB0aGUgYEZhY3RvTWluZVJgIGhhcyBhIGZ1bmN0aW9uIGBnZXRfZGlzdCgpYCB3aGljaCBJIHByZWZlciwgc2luY2UgaXQgaW5jbHVkZXMgYSBjb3VwbGUgb2Ygb3RoZXIgdXNlZnVsIGRpc3RhbmNlIG1lYXN1cnMuDQoNCmBgYHtyLGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30NCmRpc3QucmVzIDwtIGdldF9kaXN0KHJlcy5wY2EkaW5kJGNvb3JkLCBtZXRob2QgPSAiZXVjbGlkZWFuIikNCmZ2aXpfZGlzdChkaXN0LnJlcykNCmBgYA0KDQpUaGUgcmVzdWx0aW5nIGRpc3RhbmNlIG9iamVjdCBjYW4gYmUgdGFuc2Zvcm1lZCBpbiBhIGRpc3RhbmNlIG1hdHJpeC4gV2Ugd2lsbCBhbHNvIGFkZCBuYW1lcyBmb3IgdGhlIG1hdHJpeCBkaW1lbnNpb25zLg0KDQoNCmBgYHtyfQ0KZGlzdCA8LSBhcy5tYXRyaXgoZGlzdC5yZXMpDQpyb3duYW1lcyhkaXN0KSA8LSBkYXRhICU+JSBwdWxsKHBsYWNlKQ0KY29sbmFtZXMoZGlzdCkgPC0gZGF0YSAlPiUgcHVsbChwbGFjZSkNCmBgYA0KDQpUaGlzIG1hdHJpeCB3ZSBjb3VsZCwgZm9yIGV4YW1wbGUsIHVzZSB0byBjcmVhdGUgYSBkaXN0YW5jZSBtZXR3b3JrIChhcyB3ZSB3aWxsIGRvIGluIE0yKS4gV2hlbiBjb250aW51aW5nIHdpdGggInRpZHkiIGRhdGEsIHdlIHdvdWxkIGxpa2UgdG8gdHJhbnNmb3JtIGl0IGluIHdoYXQgd2UgaW4gbmV0d29yay1qYXJnb24gY2FsbCBhICJlZGdlbGlzdCIuIFNvLCB0aGF0J3MgYSBjbGFzc2ljYWwgdXNlIG9mIHRoZSBgZ2F0aGVyKClgIGZ1bmN0aW9uLiBIb3dldmVyLCBzaW5jZSB3ZSBoYXZlIGEgbWF0cml4IGhlcmUsIEkgd2lsbCB1c2UgdGhlIGBtZWx0KClgIGZ1bmN0aW9uIG9mIHRoZSBgcmVzaGFwZTJgIHBhY2thZ2UsIHNpbmNlIGl0IGF1dG9tYXRpY2FsbHkgdGlkaWVzIHRoZSBuYW1lcyBvZiBhbXRyaXggZGltZW5zaW9ucy5Ob3RpY2U6IFRoaXMgaXMgYSB2ZXJ5IGNvbnZlbmllbnQgYnV0IG5vdCB0aGUgbW9zdCBlZmZpY2llbnQgd2F5IHRvIGNyZWF0ZSBkaXN0YW5jZSBlZGdlbGlzdHMuIEluIGNhc2Ugd2UgaGF2ZSBhIHZlcnkgbGFyZ2UgbnVtYmVyIG9mIGVudGl0aWVzLCB5b3UgbWlnaHQgd2FudCB0byBsZWFybiBob3cgdG8gZGVhbCB3aXRoIHNhcnNlLW1hdHJpY2VzLiBNb3JlIG9uIHRoYXQgYWdhaW4gaW4gTTIuDQoNCmBgYHtyfQ0KIyBTb21laG93IGRvZXNudCB3b3JrDQojIGRpc3RfZWwgPC0gZGlzdCAlPiUNCiMgICBhc190aWJibGUoKSAlPiUNCiMgICByb3duYW1lc190b19jb2x1bW4odmFyID0gIlZhcjEiKSAlPiUNCiMgICBnYXRoZXIoa2V5ID0gVmFyMiwgdmFsdWUgPSBkaXN0LCAyOm5yb3coLikpIA0KDQojIEFsdGVybmF0aXZlIChtb3JlIGNvbnZlbmllbnQgaGVyZSwgYnV0IG5vdCBkcGx5ciBzdHlsZSkNCmxpYnJhcnkocmVzaGFwZTIpDQpkaXN0X2VsIDwtIG1lbHQoZGlzdCkgDQoNCmRpc3RfZWwgJTw+JQ0KICBmaWx0ZXIoVmFyMSAhPSBWYXIyKSAlPiUNCiAgYXNfdGliYmxlKCkNCg0KaGVhZChkaXN0X2VsKSAgDQpgYGANCg0KDQpPaywgbGV0cyBqdXN0IHRha2UgYSBicmllZiBsb29rIHdoaWNoIGNpdGllcyBhcmUgbW9zdCBzaW1pbGFyLCBhbmQgbW9zdCBkaXN0YW50IGluIHRlcm1zIG9mIHRoZWlyIGNoYXJhY3RlcmlzdGljcy4NCg0KYGBge3J9DQpkaXN0X2VsICU+JQ0KICBhcnJhbmdlKHZhbHVlKSAlPiUNCiAgaGVhZCgxMCkNCg0KZGlzdF9lbCAlPiUNCiAgYXJyYW5nZShkZXNjKHZhbHVlKSkgJT4lDQogIGhlYWQoMTApDQpgYGANCg0KU2lkZW5vdGU6IEhlcmUsIHdlIGNyZWF0ZWQgdGhlIGRpc3RhbmNlIGJhc2VkIG9uIGFsIGNvbXBvbmVudHMgZXF1YWxseS4gSW5zdGVhZCwgb25lIGNvdWxkIHdlaWdodCB0aGUgZGlzdGFuY2UgYnkgdGhlIGNvbXBvbmVudCdzIHZhcmlhbmNlIGV4cGxhaW5lZCwgdGhhdCB0aGUgbW9zdCBleHBsYW5hdG9yeSBjb21wb25lbnQgZ2V0cyBoaWdoZXIgd2VpZ2h0cy4gVGhhdCB3b3VsZCBiZSBhIG5pY2UgZXhlcmNpc2UuIA0KDQpTdWNoIGRpc3RhbmNlIGVkZ2VsaXN0cyBjYW4gYmUgZXh0cmVtZWx5IGluZm9ybWF0aXZlLiBIb3dldmVyLCB3ZSB3aWxsIGZvciBub3Qgbm90IHVzZSBpdCBhbnltb3JlIGluIHRoZSBhbmFseXNpcyB0byBjb21lLCBzbyBsZXRzIGdldCByaWQgb2YgdGhlIGJpZyBvYmplY3RzLg0KDQpgYGB7cn0NCnJtKGRpc3QsIGRpc3RfZWwsIGRpc3QucmVzKQ0KYGBgDQoNCg0KIyBDbHVzdGVyaW5nDQoNCiMjIEludHJvZHVjdGlvbg0KDQojIyMgVHlwZXMgb2YgQ2x1c3RlcmluZw0KQ2x1c3RlcmluZyBjYW4gYmUgYnJvYWRseSBkaXZpZGVkIGludG8gdHdvIHN1Ymdyb3VwczoNCg0KMS4gKipIYXJkIGNsdXN0ZXJpbmc6KiogaW4gaGFyZCBjbHVzdGVyaW5nLCBlYWNoIGRhdGEgb2JqZWN0IG9yIHBvaW50IGVpdGhlciBiZWxvbmdzIHRvIGEgY2x1c3RlciBjb21wbGV0ZWx5IG9yIG5vdC4gRm9yIGV4YW1wbGUgaW4gdGhlIFViZXIgZGF0YXNldCwgZWFjaCBsb2NhdGlvbiBiZWxvbmdzIHRvIGVpdGhlciBvbmUgYm9yb3VnaCBvciB0aGUgb3RoZXIuDQoyLiAqKlNvZnQgY2x1c3RlcmluZzoqKiBpbiBzb2Z0IGNsdXN0ZXJpbmcsIGEgZGF0YSBwb2ludCBjYW4gYmVsb25nIHRvIG1vcmUgdGhhbiBvbmUgY2x1c3RlciB3aXRoIHNvbWUgcHJvYmFiaWxpdHkgb3IgbGlrZWxpaG9vZCB2YWx1ZS4gRm9yIGV4YW1wbGUsIHlvdSBjb3VsZCBpZGVudGlmeSBzb21lIGxvY2F0aW9ucyBhcyB0aGUgYm9yZGVyIHBvaW50cyBiZWxvbmdpbmcgdG8gdHdvIG9yIG1vcmUgYm9yb3VnaHMuDQoNCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBjYW4gYWxzbyBiZSBjYXRlZ29yaXplZCBiYXNlZCBvbiB0aGVpciAqKmNsdXN0ZXIgbW9kZWwqKiwgdGhhdCBpcyBiYXNlZCBvbiBob3cgdGhleSBmb3JtIGNsdXN0ZXJzIG9yIGdyb3Vwcy4gVGhpcyB0dXRvcmlhbCBvbmx5IGhpZ2hsaWdodHMgc29tZSBvZiB0aGUgcHJvbWluZW50IGNsdXN0ZXJpbmcgYWxnb3JpdGhtcy4NCg0KKiAqKkNvbm5lY3Rpdml0eS1iYXNlZCBjbHVzdGVyaW5nOioqIHRoZSBtYWluIGlkZWEgYmVoaW5kIHRoaXMgY2x1c3RlcmluZyBpcyB0aGF0IGRhdGEgcG9pbnRzIHRoYXQgYXJlIGNsb3NlciBpbiB0aGUgZGF0YSBzcGFjZSBhcmUgbW9yZSByZWxhdGVkIChzaW1pbGFyKSB0aGFuIHRvIGRhdGEgcG9pbnRzIGZhcnRoZXIgYXdheS4gVGhlIGNsdXN0ZXJzIGFyZSBmb3JtZWQgYnkgY29ubmVjdGluZyBkYXRhIHBvaW50cyBhY2NvcmRpbmcgdG8gdGhlaXIgZGlzdGFuY2UuIEF0IGRpZmZlcmVudCBkaXN0YW5jZXMsIGRpZmZlcmVudCBjbHVzdGVycyB3aWxsIGZvcm0gYW5kIGNhbiBiZSByZXByZXNlbnRlZCB1c2luZyBhIGRlbmRyb2dyYW0sIHdoaWNoIGdpdmVzIGF3YXkgd2h5IHRoZXkgYXJlIGFsc28gY29tbW9ubHkgY2FsbGVkICoqaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcqKi4gVGhlc2UgbWV0aG9kcyBkbyBub3QgcHJvZHVjZSBhIHVuaXF1ZSBwYXJ0aXRpb25pbmcgb2YgdGhlIGRhdGFzZXQsIHJhdGhlciBhIGhpZXJhcmNoeSBmcm9tIHdoaWNoIHRoZSB1c2VyIHN0aWxsIG5lZWRzIHRvIGNob29zZSBhcHByb3ByaWF0ZSBjbHVzdGVycyBieSBjaG9vc2luZyB0aGUgbGV2ZWwgd2hlcmUgdGhleSB3YW50IHRvIGNsdXN0ZXIuIE5vdGU6IFRoZXkgYXJlIGFsc28gbm90IHZlcnkgcm9idXN0IHRvd2FyZHMgb3V0bGllcnMsIHdoaWNoIG1pZ2h0IHNob3cgdXAgYXMgYWRkaXRpb25hbCBjbHVzdGVycyBvciBldmVuIGNhdXNlIG90aGVyIGNsdXN0ZXJzIHRvIG1lcmdlLg0KDQoqICoqQ2VudHJvaWQtYmFzZWQgY2x1c3RlcmluZzoqKiBpbiB0aGlzIHR5cGUgb2YgY2x1c3RlcmluZywgY2x1c3RlcnMgYXJlIHJlcHJlc2VudGVkIGJ5IGEgY2VudHJhbCB2ZWN0b3Igb3IgYSBjZW50cm9pZC4gVGhpcyBjZW50cm9pZCBtaWdodCBub3QgbmVjZXNzYXJpbHkgYmUgYSBtZW1iZXIgb2YgdGhlIGRhdGFzZXQuIFRoaXMgaXMgYW4gaXRlcmF0aXZlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBpbiB3aGljaCB0aGUgbm90aW9uIG9mIHNpbWlsYXJpdHkgaXMgZGVyaXZlZCBieSBob3cgY2xvc2UgYSBkYXRhIHBvaW50IGlzIHRvIHRoZSBjZW50cm9pZCBvZiB0aGUgY2x1c3Rlci4gKiprLW1lYW5zKiogaXMgYSBjZW50cm9pZCBiYXNlZCBjbHVzdGVyaW5nLCBhbmQgd2lsbCB5b3Ugc2VlIHRoaXMgdG9waWMgbW9yZSBpbiBkZXRhaWwgbGF0ZXIgb24gaW4gdGhlIHR1dG9yaWFsLg0KDQoqICoqRGlzdHJpYnV0aW9uLWJhc2VkIGNsdXN0ZXJpbmc6KiogdGhpcyBjbHVzdGVyaW5nIGlzIHZlcnkgY2xvc2VseSByZWxhdGVkIHRvIHN0YXRpc3RpY3M6IGRpc3RyaWJ1dGlvbmFsIG1vZGVsaW5nLiBDbHVzdGVyaW5nIGlzIGJhc2VkIG9uIHRoZSBub3Rpb24gb2YgaG93IHByb2JhYmxlIGlzIGl0IGZvciBhIGRhdGEgcG9pbnQgdG8gYmVsb25nIHRvIGEgY2VydGFpbiBkaXN0cmlidXRpb24sIHN1Y2ggYXMgdGhlIEdhdXNzaWFuIGRpc3RyaWJ1dGlvbiwgZm9yIGV4YW1wbGUuIERhdGEgcG9pbnRzIGluIGEgY2x1c3RlciBiZWxvbmcgdG8gdGhlIHNhbWUgZGlzdHJpYnV0aW9uLiBUaGVzZSBtb2RlbHMgaGF2ZSBhIHN0cm9uZyB0aGVvcml0aWNhbCBmb3VuZGF0aW9uLCBob3dldmVyIHRoZXkgb2Z0ZW4gc3VmZmVyIGZyb20gb3ZlcmZpdHRpbmcuIEdhdXNzaWFuIG1peHR1cmUgbW9kZWxzLCB1c2luZyB0aGUgZXhwZWN0YXRpb24tbWF4aW1pemF0aW9uIGFsZ29yaXRobSBpcyBhIGZhbW91cyBkaXN0cmlidXRpb24gYmFzZWQgY2x1c3RlcmluZyBtZXRob2QuDQoNCiogKipEZW5zaXR5LWJhc2VkIG1ldGhvZHM6Kiogc2VhcmNoIHRoZSBkYXRhIHNwYWNlIGZvciBhcmVhcyBvZiB2YXJpZWQgZGVuc2l0eSBvZiBkYXRhIHBvaW50cy4gQ2x1c3RlcnMgYXJlIGRlZmluZWQgYXMgYXJlYXMgb2YgaGlnaGVyIGRlbnNpdHkgd2l0aGluIHRoZSBkYXRhIHNwYWNlIGNvbXBhcmVkIHRvIG90aGVyIHJlZ2lvbnMuIERhdGEgcG9pbnRzIGluIHRoZSBzcGFyc2UgYXJlYXMgYXJlIHVzdWFsbHkgY29uc2lkZXJlZCB0byBiZSBub2lzZSBhbmQvb3IgYm9yZGVyIHBvaW50cy4gVGhlIGRyYXdiYWNrIHdpdGggdGhlc2UgbWV0aG9kcyBpcyB0aGF0IHRoZXkgZXhwZWN0IHNvbWUga2luZCBvZiBkZW5zaXR5IGd1aWRlIG9yIHBhcmFtZXRlcnMgdG8gZGV0ZWN0IGNsdXN0ZXIgYm9yZGVycy4gYERCU0NBTmAgYW5kIGBPUFRJQ1NgIGFyZSBzb21lIHByb21pbmVudCBkZW5zaXR5IGJhc2VkIGNsdXN0ZXJpbmcuDQoNClNvLCB3aGF0IGlzIHRoZSBiZXN0IHRvIHVzZT8gSGFyZCB0byBzYXkuIENsdXN0ZXJpbmcgaXMgYW4gc3ViamVjdGl2ZSB0YXNrIGFuZCB0aGVyZSBjYW4gYmUgbW9yZSB0aGFuIG9uZSBjb3JyZWN0IGNsdXN0ZXJpbmcgYWxnb3JpdGhtLiBFdmVyeSBhbGdvcml0aG0gZm9sbG93cyBhIGRpZmZlcmVudCBzZXQgb2YgcnVsZXMgZm9yIGRlZmluaW5nIHRoZSAnc2ltaWxhcml0eScgYW1vbmcgZGF0YSBwb2ludHMuIFRoZSBtb3N0IGFwcHJvcHJpYXRlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIGZvciBhIHBhcnRpY3VsYXIgcHJvYmxlbSBvZnRlbiBuZWVkcyB0byBiZSBjaG9zZW4gZXhwZXJpbWVudGFsbHksIHVubGVzcyB0aGVyZSBpcyBhIG1hdGhlbWF0aWNhbCByZWFzb24gdG8gcHJlZmVyIG9uZSBjbHVzdGVyaW5nIGFsZ29yaXRobSBvdmVyIGFub3RoZXIuIEFuIGFsZ29yaXRobSBtaWdodCB3b3JrIHdlbGwgb24gYSBwYXJ0aWN1bGFyIGRhdGFzZXQgYnV0IGZhaWwgZm9yIGEgZGlmZmVyZW50IGtpbmQgb2YgZGF0YXNldC4gU2luY2UgdGhlcmUgaXMgbW9zdCB0aW1lcyBubyB3cm9uZyBvciByaWdodCwgdGhlIGNsdXN0ZXJpbmcgdGhhdCBkZWxpdmVycyB0aGUgbW9zdCB1c2VmdWwgcmVzdWx0cyBpcyB0aGUgd2F5IHRvIGdvLg0KDQojIyBLLW1lYW5zIENsdXN0ZXJpbmcNCkstbWVhbnMgY2x1c3RlcmluZyBpcyB0aGUgbW9zdCBjb21tb25seSB1c2VkIHVuc3VwZXJ2aXNlZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSBmb3IgZGl2aWRpbmcgYSBnaXZlbiBkYXRhc2V0IGludG8gYGtgIGNsdXN0ZXJzLCB3aGljaCBtdXN0IGJlIHByb3ZpZGVkIGJ5IHRoZSB1c2VyLiBUaGUgYmFzaWMgaWRlYSBiZWhpbmQgay1tZWFucyBjbHVzdGVyaW5nIGNvbnNpc3RzIG9mIGRlZmluaW5nIGNsdXN0ZXJzIHNvIHRoYXQgdGhlIHRvdGFsIGludHJhLWNsdXN0ZXIgdmFyaWF0aW9uIChrbm93biBhcyB0b3RhbCB3aXRoaW4tY2x1c3RlciB2YXJpYXRpb24pIGlzIG1pbmltaXplZC4gVGhlcmUgYXJlIHNldmVyYWwgay1tZWFucyBhbGdvcml0aG1zIGF2YWlsYWJsZS4gSG93ZXZlciwgdGhlIHN0YW5kYXJkIGFsZ29yaXRobSBkZWZpbmVzIHRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciB2YXJpYXRpb24gYXMgdGhlIHN1bSBvZiBzcXVhcmVkIGRpc3RhbmNlcyBFdWNsaWRlYW4gZGlzdGFuY2VzIGJldHdlZW4gaXRlbXMgYW5kIHRoZSBjb3JyZXNwb25kaW5nIGNlbnRyb2lkLiBJdHMgYW4gaXRlcmF0aXZlIHByb2Nlc3MgY29udGFpbmluZyB0aGUgZm9sbG93aW5nIHN0ZXBzDQoNCjEuIFNwZWNpZnkgYGtgIC0gdGhlIG51bWJlciBvZiBjbHVzdGVycyB0byBiZSBjcmVhdGVkLg0KMi4gU2VsZWN0IHJhbmRvbWx5IGBrYCBvYmplY3RzIGZyb20gdGhlIGRhdGFzZXQgYXMgdGhlIGluaXRpYWwgY2x1c3RlciBjZW50ZXJzLg0KIVtdKG1lZGlhL203X2ttMS5wbmcpe3dpZHRoPTUwMHB4fQ0KMy4gQXNzaWduIGVhY2ggb2JzZXJ2YXRpb24gdG8gdGhlaXIgY2xvc2VzdCBjZW50cm9pZCwgYmFzZWQgb24gdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBvYmplY3QgYW5kIHRoZSBjZW50cm9pZC4NCjQuIEZvciBlYWNoIG9mIHRoZSBga2AgY2x1c3RlcnMgcmVjb21wdXRlIHRoZSBjbHVzdGVyIGNlbnRyb2lkIGJ5IGNhbGN1bGF0aW5nIHRoZSBuZXcgbWVhbiB2YWx1ZSBvZiBhbGwgdGhlIGRhdGEgcG9pbnRzIGluIHRoZSBjbHVzdGVyLg0KIVtdKG1lZGlhL203X2ttMi5wbmcpe3dpZHRoPTUwMHB4fQ0KNS4gSXRlcmF0aXZlbHkgbWluaW1pemUgdGhlIHRvdGFsIHdpdGhpbiBzdW0gb2Ygc3F1YXJlLiBSZXBlYXQgU3RlcCAzIGFuZCBTdGVwIDQsIHVudGlsIHRoZSBjZW50cm9pZHMgZG8gbm90IGNoYW5nZSBvciB0aGUgbWF4aW11bSBudW1iZXIgb2YgaXRlcmF0aW9ucyBpcyByZWFjaGVkIChgUmAgdXNlcyAxMCBhcyB0aGUgZGVmYXVsdCB2YWx1ZSBmb3IgdGhlIG1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMpLg0KIVtdKG1lZGlhL203X2ttMy5wbmcpe3dpZHRoPTUwMHB4fQ0KDQpTbywgbGV0cyBkbyB0aGF0LiBBcyBhbHJlYWR5IG1lbnRpb25lZCwgd2UgaGF2ZSB0byB1cGZyb250IGNob29zZSBvdXIgYGtgLiBIb3dldmVyLCB0aGVyZSBleGlzdHMgc29tZSBndWlkYW5jZSwgZm9yIGV4YW1wbGUgdGhlIGhpZ2hlc3QgZ2FpbiBpbiAidG90YWwgd2l0aGluIHN1bSBvZiBzcWFyZXMiIChmYXN0IHRvIGNhbGN1bGF0ZSksIHRoZSAic2lsdWV0dGUiLCBhcyB3ZWxsIGFzIHRoZSAiZ2FwIHN0YXRpc3RpY3MiIChoYXJkIHRvIGNhbGN1bGF0ZSwgdGFrZXMgdGltZSkuDQoNCmBgYHtyLGZpZy5hbGlnbj0nY2VudGVyJ30NCmZ2aXpfbmJjbHVzdChzY2FsZShkYXRhWyx2YXJzXSksIA0KICAgICAgICAgICAgIGttZWFucywgDQogICAgICAgICAgICAgbWV0aG9kID0gIndzcyIpICANCg0KYGBgDQoNCk9rLHcgZSBoZXJlIHNldHRsZSBmb3IgMyAoZXhlY3V0aXZlIGRlc2ljaW9uKS4gQmVmb3JlIHdlIHN0YXJ0LCBzb21ldGhpbmcgd2VpcmQgdXBmcm9udC4gVGhlIGZ1bmN0aW9uIHRha2VzIHRoZSBvYnNlcnZhdGlvbiBuYW1lcyBmcm9tIHRoZSByb3duYW1lcyAod2hpY2ggbm9ib2R5IHVzZXMgYW55bW9yZSwgYW5kIGFyZSBkZXByZWNpYXRlZCBieSBgZHBseXJgKS4gU28sIHJlbWViZXIgdG8gZGVmaW5lIHRoZW0ganVzdCBzdHJhaWdodCBiZWZvcmUgeW91IGNsdXN0ZXIsIG90aGVyd2lzZSB0aGUgbmV4dCBgZHBseXJgIHBpcGUgd2lsbCBkZWxldGUgdGhlbSBhZ2Fpbi4NCg0KYGBge3J9DQpyb3duYW1lcyhkYXRhKSA8LSBkYXRhICU+JSBwdWxsKHBsYWNlKQ0KYGBgDQoNCk9mbGV0cyBydW4gdGhlIGFsZ29yeXRobS4NCg0KYGBge3J9DQprbSA8LSBrbWVhbnMoc2NhbGUoZGF0YVssdmFyc10pLCBjZW50ZXJzID0gMywgbnN0YXJ0ID0gMjApICANCmdsaW1wc2Uoa20pDQpgYGANCg0KQWdhaW4sIGxldHMgdmlzdWFsaXplIGl0LiBUbyBoYXZlIGEgbWVhbmluZ2Z1bCB3YXkgZm9yIDJkIHZpc3VhbGl6YXRpb24sIHdlIGFnYWluIHByb2plY3QgdGhlIG9ic2VydmF0aW9ucyBvbiB0aGUgc3BhY2Ugb2YgdGhlIGZpcnN0IDIgY29tcG9uZW50cy4NCg0KYGBge3IsLGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30NCmZ2aXpfY2x1c3RlcihrbSwgZGF0YSA9IGRhdGFbLHZhcnNdLA0KICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9ncmF5KCkpICANCmBgYA0KDQpPaywgd2UgZ290IDMgY2x1c3RlcnMuIExldCdzIGxvb2sgd2hhdCdzIGluIHRoZW0uDQoNCmBgYHtyfQ0KZGF0YSAlPiUNCiAgYmluZF9jb2xzKGNsdXN0ZXIgPSBrbSRjbHVzdGVyKSAlPiUNCiAgc2VsZWN0KHZhcnMuZGVzYywgY2x1c3RlcikgJT4lDQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQ0KICBtdXRhdGUobiA9IG4oKSkgJT4lDQogIHN1bW1hcmlzZV9hbGwoZnVucyhtZWFuKSkNCmBgYA0KDQoNCiMjIEhpcmFyY2hpY2FsIENsdXN0ZXJpbmcNCg0KIyMjIEludHJvZHVjdGlvbg0KVGhlIGtleSBvcGVyYXRpb24gaW4gaGllcmFyY2hpY2FsIGFnZ2xvbWVyYXRpdmUgY2x1c3RlcmluZyBpcyB0byByZXBlYXRlZGx5IGNvbWJpbmUgdGhlIHR3byBuZWFyZXN0IGNsdXN0ZXJzIGludG8gYSBsYXJnZXIgY2x1c3Rlci4gVGhlcmUgYXJlIHRocmVlIGtleSBxdWVzdGlvbnMgdGhhdCBuZWVkIHRvIGJlIGFuc3dlcmVkIGZpcnN0Og0KDQoqIEhvdyBkbyB5b3UgcmVwcmVzZW50IGEgY2x1c3RlciBvZiBtb3JlIHRoYW4gb25lIHBvaW50Pw0KKiBIb3cgZG8geW91IGRldGVybWluZSB0aGUgIm5lYXJuZXNzIiBvZiBjbHVzdGVycz8NCiogV2hlbiBkbyB5b3Ugc3RvcCBjb21iaW5pbmcgY2x1c3RlcnM/DQoqIEhvcGVmdWxseSBieSB0aGUgZW5kIHRoaXMgdHV0b3JpYWwgeW91IHdpbGwgYmUgYWJsZSB0byBhbnN3ZXIgYWxsIG9mIHRoZXNlIHF1ZXN0aW9ucy4gQmVmb3JlIGFwcGx5aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGxldCdzIGhhdmUgYSBsb29rIGF0IGl0cyB3b3JraW5nOg0KDQoxLiBJdCBzdGFydHMgYnkgY2FsY3VsYXRpbmcgdGhlIGRpc3RhbmNlIGJldHdlZW4gZXZlcnkgcGFpciBvZiBvYnNlcnZhdGlvbiBwb2ludHMgYW5kIHN0b3JlIGl0IGluIGEgZGlzdGFuY2UgbWF0cml4Lg0KMi4gSXQgdGhlbiBwdXRzIGV2ZXJ5IHBvaW50IGluIGl0cyBvd24gY2x1c3Rlci4NCg0KIVtdKG1lZGlhL203X2hjMS5wbmcpe3dpZHRoPTUwMHB4fQ0KDQozLiBUaGVuIGl0IHN0YXJ0cyBtZXJnaW5nIHRoZSBjbG9zZXN0IHBhaXJzIG9mIHBvaW50cyBiYXNlZCBvbiB0aGUgZGlzdGFuY2VzIGZyb20gdGhlIGRpc3RhbmNlIG1hdHJpeCBhbmQgYXMgYSByZXN1bHQgdGhlIGFtb3VudCBvZiBjbHVzdGVycyBnb2VzIGRvd24gYnkgMS4NCg0KIVtdKG1lZGlhL203X2hjMi5wbmcpe3dpZHRoPTUwMHB4fQ0KDQo0LiBUaGVuIGl0IHJlY29tcHV0ZXMgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIG5ldyBjbHVzdGVyIGFuZCB0aGUgb2xkIG9uZXMgYW5kIHN0b3JlcyB0aGVtIGluIGEgbmV3IGRpc3RhbmNlIG1hdHJpeC4NCjUuIExhc3RseSBpdCByZXBlYXRzIHN0ZXBzIDIgYW5kIDMgdW50aWwgYWxsIHRoZSBjbHVzdGVycyBhcmUgbWVyZ2VkIGludG8gb25lIHNpbmdsZSBjbHVzdGVyLg0KDQohW10obWVkaWEvbTdfaGMzLnBuZyl7d2lkdGg9NTAwcHh9DQoNClRoZXJlIGFyZSBzZXZlcmFsIHdheXMgdG8gbWVhc3VyZSB0aGUgZGlzdGFuY2UgYmV0d2VlbiBjbHVzdGVycyBpbiBvcmRlciB0byBkZWNpZGUgdGhlIHJ1bGVzIGZvciBjbHVzdGVyaW5nLCBhbmQgdGhleSBhcmUgb2Z0ZW4gY2FsbGVkIExpbmthZ2UgTWV0aG9kcy4gU29tZSBvZiB0aGUgY29tbW9uIGxpbmthZ2UgbWV0aG9kcyBhcmU6DQoNCiogKipDb21wbGV0ZS1saW5rYWdlOioqIGNhbGN1bGF0ZXMgdGhlIG1heGltdW0gZGlzdGFuY2UgYmV0d2VlbiBjbHVzdGVycyBiZWZvcmUgbWVyZ2luZy4NCiogKipTaW5nbGUtbGlua2FnZToqKiBjYWxjdWxhdGVzIHRoZSBtaW5pbXVtIGRpc3RhbmNlIGJldHdlZW4gdGhlIGNsdXN0ZXJzIGJlZm9yZSBtZXJnaW5nLiBUaGlzIGxpbmthZ2UgbWF5IGJlIHVzZWQgdG8gZGV0ZWN0IGhpZ2ggdmFsdWVzIGluIHlvdXIgZGF0YXNldCB3aGljaCBtYXkgYmUgb3V0bGllcnMgYXMgdGhleSB3aWxsIGJlIG1lcmdlZCBhdCB0aGUgZW5kLg0KKiAqKkF2ZXJhZ2UtbGlua2FnZToqKiBjYWxjdWxhdGVzIHRoZSBhdmVyYWdlIGRpc3RhbmNlIGJldHdlZW4gY2x1c3RlcnMgYmVmb3JlIG1lcmdpbmcuDQoqICoqQ2VudHJvaWQtbGlua2FnZToqKiBmaW5kcyBjZW50cm9pZCBvZiBjbHVzdGVyIDEgYW5kIGNlbnRyb2lkIG9mIGNsdXN0ZXIgMiwgYW5kIHRoZW4gY2FsY3VsYXRlcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdHdvIGJlZm9yZSBtZXJnaW5nIChoaW50OiB1c3VhbGx5IG5vdCBhIGdvb2QgaWRlYSkuDQoNClRoZSBjaG9pY2Ugb2YgbGlua2FnZSBtZXRob2QgZW50aXJlbHkgZGVwZW5kcyBvbiB5b3UgYW5kIHRoZXJlIGlzIG5vIGhhcmQgYW5kIGZhc3QgbWV0aG9kIHRoYXQgd2lsbCBhbHdheXMgZ2l2ZSB5b3UgZ29vZCByZXN1bHRzLiBEaWZmZXJlbnQgbGlua2FnZSBtZXRob2RzIGxlYWQgdG8gZGlmZmVyZW50IGNsdXN0ZXJzLg0KDQpTb21lIGZ1cnRoZXIgcHJhY3RpY2FsIGlzc3VlczoNCg0KKiBEYXRhIG9uIGRpZmZlcmVudCBzY2FsZXMgY2FuIGNhdXNlIHVuZGVzaXJhYmxlIHJlc3VsdHNpbiBjbHVzdGVyaW5nIG1ldGhvZHMNCiogU29sdXRpb24gaXMgdG8gc2NhbGUgZGF0YSBzbyB0aGF0IGZlYXR1cmVzIGhhdmUgc2FtZSBtZWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24NCiogU3VidHJhY3QgbWVhbiBvZiBhIGZlYXR1cmUgZnJvbSBhbGwgb2JzZXJ2YXRpb25zLCBEaXZpZGUgZWFjaCBmZWF0dXJlIGJ5IHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2Z0aGUgZmVhdHVyZQ0KKiBOb3JtYWxpemVkIGZlYXR1cmVzIGhhdmUgYSBtZWFuIG9mIHplcm8gYW5kIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIG9uZQ0KDQojIyMgUGVyZm9ybWluZyBhIGhpcmFyY2hpY2FsIGNsdXN0ZXJpbmcNCkhvd2V2ZXIsIGxldCdzIGdldCBpdCBzdGFydGVkIGFuZCBwZXJmb3JtIGEgY2x1c3Rlci4gV2UgaGVyZSB1c2UgdGhlIGBoY3V0YCBmdW5jdGlvbiwgd2hpY2ggaW5jbHVkZXMgbW9zdCBvZiB0aGUgYWJvdmVtZW50aW9uZWQgbWFwcHJvYWNoZXMgYXMgb3B0aW9ucy4NCg0KYGBge3J9DQpoYyA8LSBoY3V0KGRhdGFbLHZhcnNdLCBoY19mdW5jID0gImhjbHVzdCIsIGsgPSAzLCBzdGFuZCA9IFRSVUUpDQpgYGANCg0KSW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsIHlvdSBjYXRlZ29yaXplIHRoZSBvYmplY3RzIGludG8gYSBoaWVyYXJjaHkgc2ltaWxhciB0byBhIHRyZWUtbGlrZSBkaWFncmFtIHdoaWNoIGlzIGNhbGxlZCBhIGRlbmRyb2dyYW0uIFRoZSBkaXN0YW5jZSBvZiBzcGxpdCBvciBtZXJnZSAoY2FsbGVkIGhlaWdodCkgaXMgc2hvd24gb24gdGhlIHktYXhpcyBvZiB0aGUgZGVuZHJvZ3JhbSBiZWxvdy4NCg0KYGBge3IsLGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30NCmZ2aXpfZGVuZChoYywgDQogICAgICAgICAgcmVjdCA9IFRSVUUsIA0KICAgICAgICAgIGNleCA9IDAuNSkNCmBgYA0KDQoqKk5vdGljZSoqIGhvdyB0aGUgZGVuZHJvZ3JhbSBpcyBidWlsdCBhbmQgZXZlcnkgZGF0YSBwb2ludCBmaW5hbGx5IG1lcmdlcyBpbnRvIGEgc2luZ2xlIGNsdXN0ZXIgd2l0aCB0aGUgaGVpZ2h0KGRpc3RhbmNlKSBzaG93biBvbiB0aGUgeS1heGlzLg0KDQpMZXQncyBpbnNwZWN0IHdoYXQncyBpbiB0aGUgY2x1c3RlcnMuDQoNCmBgYHtyfQ0KZGF0YSAlPiUNCiAgYmluZF9jb2xzKGNsdXN0ZXIgPSBoYyRjbHVzdGVyKSAlPiUNCiAgc2VsZWN0KHZhcnMuZGVzYywgY2x1c3RlcikgJT4lDQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQ0KICBtdXRhdGUobiA9IG4oKSkgJT4lDQogIHN1bW1hcmlzZV9hbGwoZnVucyhtZWFuKSkNCmBgYA0KDQpBbmQgYWdhaW4gdmlzdWFsaXplIHRoZW06DQoNCmBgYHtyLCxmaWcud2lkdGg9MTUsZmlnLmhlaWdodD0xMCxmaWcuYWxpZ249J2NlbnRlcid9DQpmdml6X2NsdXN0ZXIoaGMsIGRhdGEgPSBkYXRhWyx2YXJzXSwNCiAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKSAgDQpgYGANCg0KTG9va3MgdmVyeSBzaW1pbGFyLCBldmVuIHRob3VnaCB0aGUgbWlkZGxlIGNsdXN0ZXIgaXMgYSBiaXQgbW9yZSBzcWVlemVkIGluIGJldHdlZW4gbm93LiBXZSBjYW4gYWxzbyB1c2Ugb3VyIHNjYXR0ZXJwbG90IGRpYWdub3N0aWNzIGFnYWluLCBhbmQgY29sb3IgdGhlIG9ic2VydmF0aW9ucyBieSB0aGVpciBjbHVzdGVyIGFzc2lnbm1lbnQuDQoNCmBgYHtyLCxmaWcud2lkdGg9MTUsZmlnLmhlaWdodD0xNSxmaWcuYWxpZ249J2NlbnRlcid9DQpnZ3BhaXJzKGRhdGFbLHZhcnMuZGVzY10sIA0KICAgICAgICBsb3dlciA9IGxpc3QoY29udGludW91cyA9ICJzbW9vdGgiKSwgDQogICAgICAgIGFlcyhjb2xvdXIgPSBhcy5mYWN0b3IoaGMkY2x1c3RlciksIGFscGhhID0gMC40KSwNCiAgICAgICAgcHJvZ3Jlc3MgPSBGQUxTRSwNCiAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2dyYXkoKSApDQpgYGANCg0KDQojIyMgSGlyYXJjaGljYWwgQ2x1c3RlcmluZyBiYXNlZCBpbiBQQ0ENCllvdSBtaWdodCBhbHJlYWR5IGhhdmUgd29uZGVyZWQ6ICJDT3VsZCBvbmUgY29tYmluZSBhIFBDQSB3aXRoIGNsdXN0ZXJpbmcgdGVjaG5pcXVlcyI/IFRoZSBhbnN3ZXIgaXM6ICJZZXMhIi4gSW4gcHJhY3RpY2UsIHRoYXQgYWN0dWFsbHkgd29ya3MgdmVyeSBmaW5lLCBhbmQgb2Z0ZW4gZGVsaXZlcnMgbW9yZSByb2J1c3QgY2x1c3RlcnMuIFNvLCBsZXRzIGdpdmUgaXQgYSBzaG90LiBXZSBjb3VsZCBkbyBpdCBieSBoYW5kLCBidXQgdGhlIGBIQ1BDYCBmdW5jdGlvbiBhbHJlYWR5IGRvZXMgdGhhdCBmb3IgdXMsIGFuZCBvZmZlcnMgYWxzbyBhIG5pY2UgZGlhZ25vc3RpYyB2aXouDQoNCg0KYGBge3J9DQpoY3BjIDwtIEhDUEMocmVzLnBjYSwgDQogICAgICAgICAgICAgbmIuY2x1c3QgPSAtMSwgIyAgc2VsZiBkZXRlcm1pbmVkOiBoaWdoZXIgcmVsYXRpdmUgbG9zcyBvZiBpbmVydGlhDQogICAgICAgICAgICAgZ3JhcGggPSBGQUxTRSkgDQpgYGANCg0KYGBge3IsLGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30NCnBsb3QoaGNwYywgY2hvaWNlID0gIjNELm1hcCIpDQpgYGANCg0KDQpUbyBmaW5pc2ggdXAsIGxldHMgcGxvdCBpdCBpbiBhIG1hcCwgc2ltcGxlc3Qgd2F5IHBvc3NpYmxlLg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dtYXApDQptYXBXb3JsZCA8LSBib3JkZXJzKCJ3b3JsZCIsIGNvbG91ciA9ICJncmF5NTAiLCBmaWxsID0gImdyYXk1MCIpDQptcCA8LSBnZ3Bsb3QoKSArICAgbWFwV29ybGQgDQptcCArIGdlb21fcG9pbnQoYWVzKHggPSBkYXRhJGxvbmdpdHVkZSwgeSA9IGRhdGEkbGF0aXR1ZGUpICwgY29sb3IgPSBoY3BjJGRhdGEuY2x1c3QkY2x1c3QpIA0KICANCmBgYA0KDQojIyMgTm90ZTogQ29tcGFyaW5nIHdpdGggSy1NZWFucyBjbHVzdGVyaW5nIGFsZ29yaXRobQ0KWW91IG1pZ2h0IGhhdmUgaGVhcmQgYWJvdXQgdGhlIGstbWVhbnMgY2x1c3RlcmluZyBhbGdvcml0aG07IGlmIG5vdCwgdGFrZSBhIGxvb2sgYXQgdGhpcyB0dXRvcmlhbC4gVGhlcmUgYXJlIG1hbnkgZnVuZGFtZW50YWwgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgdHdvIGFsZ29yaXRobXMsIGFsdGhvdWdoIGFueSBvbmUgY2FuIHBlcmZvcm0gYmV0dGVyIHRoYW4gdGhlIG90aGVyIGluIGRpZmZlcmVudCBjYXNlcy4gU29tZSBvZiB0aGUgZGlmZmVyZW5jZXMgYXJlOg0KDQoqICoqRGlzdGFuY2UgdXNlZDoqKiBIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBjYW4gdmlydHVhbGx5IGhhbmRsZSBhbnkgZGlzdGFuY2UgbWV0cmljIHdoaWxlIGstbWVhbnMgcmVseSBvbiBldWNsaWRlYW4gZGlzdGFuY2VzLg0KKiAqKlN0YWJpbGl0eSBvZiByZXN1bHRzOioqIGstbWVhbnMgcmVxdWlyZXMgYSByYW5kb20gc3RlcCBhdCBpdHMgaW5pdGlhbGl6YXRpb24gdGhhdCBtYXkgeWllbGQgZGlmZmVyZW50IHJlc3VsdHMgaWYgdGhlIHByb2Nlc3MgaXMgcmUtcnVuLiBUaGF0IHdvdWxkbid0IGJlIHRoZSBjYXNlIGluIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLg0KKiAqKk51bWJlciBvZiBDbHVzdGVyczoqKiBXaGlsZSB5b3UgY2FuIHVzZSBlbGJvdyBwbG90cywgU2lsaG91ZXR0ZSBwbG90IGV0Yy4gdG8gZmlndXJlIHRoZSByaWdodCBudW1iZXIgb2YgY2x1c3RlcnMgaW4gay1tZWFucywgaGllcmFyY2hpY2FsIHRvbyBjYW4gdXNlIGFsbCBvZiB0aG9zZSBidXQgd2l0aCB0aGUgYWRkZWQgYmVuZWZpdCBvZiBsZXZlcmFnaW5nIHRoZSBkZW5kcm9ncmFtIGZvciB0aGUgc2FtZS4NCiogKipDb21wdXRhdGlvbiBDb21wbGV4aXR5OioqIEstbWVhbnMgaXMgbGVzcyBjb21wdXRhdGlvbmFsbHkgZXhwZW5zaXZlIHRoYW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYW5kIGNhbiBiZSBydW4gb24gbGFyZ2UgZGF0YXNldHMgd2l0aGluIGEgcmVhc29uYWJsZSB0aW1lIGZyYW1lLCB3aGljaCBpcyB0aGUgbWFpbiByZWFzb24gay1tZWFucyBpcyBtb3JlIHBvcHVsYXIuDQoNCiMjIyBOb3RlOiBNZWFzdXJpbmcgR29kbmVzcy1vZi1GaXQgaW4gY2x1c3RlcnMNClBlcmhhcHMgdGhlIG1vc3QgaW1wb3J0YW50IHBhcnQgaW4gYW55IHVuc3VwZXJ2aXNlZCBsZWFybmluZyB0YXNrIGlzIHRoZSBhbmFseXNpcyBvZiB0aGUgcmVzdWx0cy4gQWZ0ZXIgeW91IGhhdmUgcGVyZm9ybWVkIHRoZSBjbHVzdGVyaW5nIHVzaW5nIGFueSBhbGdvcml0aG0gYW5kIGFueSBzZXRzIG9mIHBhcmFtZXRlcnMgeW91IG5lZWQgdG8gbWFrZSBzdXJlIHRoYXQgeW91IGRpZCBpdCByaWdodC4gQnV0IGhvdyBkbyB5b3UgZGV0ZXJtaW5lIHRoYXQ/DQoNCldlbGwsIHRoZXJlIGFyZSBtYW55IG1lYXN1cmVzIHRvIGRvIHRoaXMsIHBlcmhhcHMgdGhlIG1vc3QgcG9wdWxhciBvbmUgaXMgdGhlIER1bm4ncyBJbmRleC4gRHVubidzIGluZGV4IGlzIHRoZSByYXRpbyBiZXR3ZWVuIHRoZSBtaW5pbXVtIGludGVyLWNsdXN0ZXIgZGlzdGFuY2VzIHRvIHRoZSBtYXhpbXVtIGludHJhLWNsdXN0ZXIgZGlhbWV0ZXIuIFRoZSBkaWFtZXRlciBvZiBhIGNsdXN0ZXIgaXMgdGhlIGRpc3RhbmNlIGJldHdlZW4gaXRzIHR3byBmdXJ0aGVybW9zdCBwb2ludHMuIEluIG9yZGVyIHRvIGhhdmUgd2VsbCBzZXBhcmF0ZWQgYW5kIGNvbXBhY3QgY2x1c3RlcnMgeW91IHNob3VsZCBhaW0gZm9yIGEgaGlnaGVyIER1bm4ncyBpbmRleC4NCg0KRnVydGhlcm1vcmUsIGdyYXBoaWNhbCBpbnNwZWN0aW9uIG9mdGVuIGhlbHBzIGNvbXBhcmluZyB0aGUgcmVzdWx0cyBvZiBkaWZmZXJlbnQgYWxnb3JpdGhtcyBhbmQgcG9hcmFtZXRlcnMuIFtIZXJlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGVuZGV4dGVuZC92aWduZXR0ZXMvQ2x1c3Rlcl9BbmFseXNpcy5odG1sKSB5b3UgZmluZCBzb21lIGFkdmFuY2VkIGRpYWdub3N0aWMgdmlzdWFsaXphdGlvbnMgZm9yIGhpcmFyY2hpY2FsIGNsdXN0ZXJpbmcuDQoNCkxhc3RseSwgYSBjbHVzdGVycyBxdWFsaXR5IGlzIHRvIGEgbGFyZ2UgZXh0ZW5kIGRldGVybWluZWQgYnkgaXRzIHVzZWZ1bG5lc3MuDQoqIEludGVybmFsIFZhbGlkaXR5DQoqIEV4dGVybmFsIFZhbGlkaXR5DQoNCiMgWW91ciB0dXJuIQ0KU28sIHdoeSBub3QgaGF2ZSBzb21lIGZ1biBvbiB5b3VyIG93biBub3c/IHRyeSB0byB1c2Ugd2hhdCB5b3UgbGVhcm5lZCB1cCB0byBub3cgaW4gdGhlIGZvbGxvd2luZyBleHRlcmNpc2UuIFstLS0+IEhFUkUgPC0tLV0oaHR0cHM6Ly9naXRodWIuY29tL1NEUy1BQVUvTTEtMjAxOC9ibG9iL21hc3Rlci9kYXRhL3ZzbTEzLmNzdikgeW91IHdpbGwgZmluZCBhIGRhdGFzZXQgb24gR2VydCBIb2ZzdGVkZSdzIFsiNi1EIG1vZGVsIG9mIG5hdGlvbmFsIGN1bHR1cmUiIl0oaHR0cHM6Ly9nZWVydGhvZnN0ZWRlLmNvbS9jdWx0dXJlLWdlZXJ0LWhvZnN0ZWRlLWdlcnQtamFuLWhvZnN0ZWRlLzZkLW1vZGVsLW9mLW5hdGlvbmFsLWN1bHR1cmUvKS4gVGhpcyBwb3B1bGFyIG1lYXN1cmVzIG9mIGNvdW50cnktbGV2ZWwgY3VsdHVyZSBpbiAoYnkgbm93KSA2IGRpbWVuc2lvbnMgYmVjYW1lIHZlcnkgcG9wdWxhciBpbiBzb2Npb2xvZ3ksIGVjb25vbWljcywgYW5kIG1hbmFnZW1lbnQgc2NpZW5jZSB0byBleHBsYWluIGNyb3NzLWN1bHR1cmFsIGludGVyYWN0aW9uIGFzIHdlbGwgYXMgZnJpY3Rpb25zLiBhIGV4aGF1c3RpdmUgZG9jdW1lbnRhdGlvbiBvZiB0aGUgMjAxMyBkYXRhc2V0IGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9nZWVydGhvZnN0ZWRlLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNi8wNy9NYW51YWwtVlNNLTIwMTMucGRmKS4gSXQgY29udGFpbnMgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMuDQoNCiogKipgcGRpOmAqKiAgVGhlIHBvd2VyIGRpc3RhbmNlIGluZGV4IGlzIGRlZmluZWQgYXMgInRoZSBleHRlbnQgdG8gd2hpY2ggdGhlIGxlc3MgcG93ZXJmdWwgbWVtYmVycyBvZiBvcmdhbml6YXRpb25zIGFuZCBpbnN0aXR1dGlvbnMgKGxpa2UgdGhlIGZhbWlseSkgYWNjZXB0IGFuZCBleHBlY3QgdGhhdCBwb3dlciBpcyBkaXN0cmlidXRlZCB1bmVxdWFsbHkuIkluIHRoaXMgZGltZW5zaW9uLCBpbmVxdWFsaXR5IGFuZCBwb3dlciBpcyBwZXJjZWl2ZWQgZnJvbSB0aGUgZm9sbG93ZXJzLCBvciB0aGUgbG93ZXIgbGV2ZWwuIEEgaGlnaGVyIGRlZ3JlZSBvZiB0aGUgSW5kZXggaW5kaWNhdGVzIHRoYXQgaGllcmFyY2h5IGlzIGNsZWFybHkgZXN0YWJsaXNoZWQgYW5kIGV4ZWN1dGVkIGluIHNvY2lldHksIHdpdGhvdXQgZG91YnQgb3IgcmVhc29uLiBBIGxvd2VyIGRlZ3JlZSBvZiB0aGUgSW5kZXggc2lnbmlmaWVzIHRoYXQgcGVvcGxlIHF1ZXN0aW9uIGF1dGhvcml0eSBhbmQgYXR0ZW1wdCB0byBkaXN0cmlidXRlIHBvd2VyLg0KKiAqKmBpZHY6YCoqICBUaGlzIGluZGV4IGV4cGxvcmVzIHRoZSAiZGVncmVlIHRvIHdoaWNoIHBlb3BsZSBpbiBhIHNvY2lldHkgYXJlIGludGVncmF0ZWQgaW50byBncm91cHMuIiIgSW5kaXZpZHVhbGlzdGljIHNvY2lldGllcyBoYXZlIGxvb3NlIHRpZXMgdGhhdCBvZnRlbiBvbmx5IHJlbGF0ZXMgYW4gaW5kaXZpZHVhbCB0byBoaXMvaGVyIGltbWVkaWF0ZSBmYW1pbHkuIFRoZXkgZW1waGFzaXplIHRoZSAiSSIgdmVyc3VzIHRoZSAid2UiLiBJdHMgY291bnRlcnBhcnQsIGNvbGxlY3RpdmlzbSwgZGVzY3JpYmVzIGEgc29jaWV0eSBpbiB3aGljaCB0aWdodGx5LWludGVncmF0ZWQgcmVsYXRpb25zaGlwcyB0aWUgZXh0ZW5kZWQgZmFtaWxpZXMgYW5kIG90aGVycyBpbnRvIGluLWdyb3Vwcy4gVGhlc2UgaW4tZ3JvdXBzIGFyZSBsYWNlZCB3aXRoIHVuZG91YnRlZCBsb3lhbHR5IGFuZCBzdXBwb3J0IGVhY2ggb3RoZXIgd2hlbiBhIGNvbmZsaWN0IGFyaXNlcyB3aXRoIGFub3RoZXIgaW4tZ3JvdXAuDQoqICoqYG1hczpgKiogIEluIHRoaXMgZGltZW5zaW9uLCBtYXNjdWxpbml0eSBpcyBkZWZpbmVkIGFzICJhIHByZWZlcmVuY2UgaW4gc29jaWV0eSBmb3IgYWNoaWV2ZW1lbnQsIGhlcm9pc20sIGFzc2VydGl2ZW5lc3MgYW5kIG1hdGVyaWFsIHJld2FyZHMgZm9yIHN1Y2Nlc3MuIiIgSXRzIGNvdW50ZXJwYXJ0IHJlcHJlc2VudHMgImEgcHJlZmVyZW5jZSBmb3IgY29vcGVyYXRpb24sIG1vZGVzdHksIGNhcmluZyBmb3IgdGhlIHdlYWsgYW5kIHF1YWxpdHkgb2YgbGlmZS4iIFdvbWVuIGluIHRoZSByZXNwZWN0aXZlIHNvY2lldGllcyB0ZW5kIHRvIGRpc3BsYXkgZGlmZmVyZW50IHZhbHVlcy4gSW4gZmVtaW5pbmUgc29jaWV0aWVzLCB0aGV5IHNoYXJlIG1vZGVzdCBhbmQgY2FyaW5nIHZpZXdzIGVxdWFsbHkgd2l0aCBtZW4uIEluIG1vcmUgbWFzY3VsaW5lIHNvY2lldGllcywgd29tZW4gYXJlIHNvbWV3aGF0IGFzc2VydGl2ZSBhbmQgY29tcGV0aXRpdmUsIGJ1dCBub3RhYmx5IGxlc3MgdGhhbiBtZW4uIEluIG90aGVyIHdvcmRzLCB0aGV5IHN0aWxsIHJlY29nbml6ZSBhIGdhcCBiZXR3ZWVuIG1hbGUgYW5kIGZlbWFsZSB2YWx1ZXMuIFRoaXMgZGltZW5zaW9uIGlzIGZyZXF1ZW50bHkgdmlld2VkIGFzIHRhYm9vIGluIGhpZ2hseSBtYXNjdWxpbmUgc29jaWV0aWVzLg0KKiAqKmB1YWk6YCoqICBUaGUgdW5jZXJ0YWludHkgYXZvaWRhbmNlIGluZGV4IGlzIGRlZmluZWQgYXMgImEgc29jaWV0eSdzIHRvbGVyYW5jZSBmb3IgYW1iaWd1aXR5LCIgaW4gd2hpY2ggcGVvcGxlIGVtYnJhY2Ugb3IgYXZlcnQgYW4gZXZlbnQgb2Ygc29tZXRoaW5nIHVuZXhwZWN0ZWQsIHVua25vd24sIG9yIGF3YXkgZnJvbSB0aGUgc3RhdHVzIHF1by4gU29jaWV0aWVzIHRoYXQgc2NvcmUgYSBoaWdoIGRlZ3JlZSBpbiB0aGlzIGluZGV4IG9wdCBmb3Igc3RpZmYgY29kZXMgb2YgYmVoYXZpb3IsIGd1aWRlbGluZXMsIGxhd3MsIGFuZCBnZW5lcmFsbHkgcmVseSBvbiBhYnNvbHV0ZSB0cnV0aCwgb3IgdGhlIGJlbGllZiB0aGF0IG9uZSBsb25lIHRydXRoIGRpY3RhdGVzIGV2ZXJ5dGhpbmcgYW5kIHBlb3BsZSBrbm93IHdoYXQgaXQgaXMuIEEgbG93ZXIgZGVncmVlIGluIHRoaXMgaW5kZXggc2hvd3MgbW9yZSBhY2NlcHRhbmNlIG9mIGRpZmZlcmluZyB0aG91Z2h0cyBvciBpZGVhcy4gU29jaWV0eSB0ZW5kcyB0byBpbXBvc2UgZmV3ZXIgcmVndWxhdGlvbnMsIGFtYmlndWl0eSBpcyBtb3JlIGFjY3VzdG9tZWQgdG8sIGFuZCB0aGUgZW52aXJvbm1lbnQgaXMgbW9yZSBmcmVlLWZsb3dpbmcuDQoqICoqYGx0b3d2czpgKiogVGhpcyBkaW1lbnNpb24gYXNzb2NpYXRlcyB0aGUgY29ubmVjdGlvbiBvZiB0aGUgcGFzdCB3aXRoIHRoZSBjdXJyZW50IGFuZCBmdXR1cmUgYWN0aW9ucy9jaGFsbGVuZ2VzLiBBIGxvd2VyIGRlZ3JlZSBvZiB0aGlzIGluZGV4IChzaG9ydC10ZXJtKSBpbmRpY2F0ZXMgdGhhdCB0cmFkaXRpb25zIGFyZSBob25vcmVkIGFuZCBrZXB0LCB3aGlsZSBzdGVhZGZhc3RuZXNzIGlzIHZhbHVlZC4gU29jaWV0aWVzIHdpdGggYSBoaWdoIGRlZ3JlZSBpbiB0aGlzIGluZGV4IChsb25nLXRlcm0pIHZpZXdzIGFkYXB0YXRpb24gYW5kIGNpcmN1bXN0YW50aWFsLCBwcmFnbWF0aWMgcHJvYmxlbS1zb2x2aW5nIGFzIGEgbmVjZXNzaXR5LiBBIHBvb3IgY291bnRyeSB0aGF0IGlzIHNob3J0LXRlcm0gb3JpZW50ZWQgdXN1YWxseSBoYXMgbGl0dGxlIHRvIG5vIGVjb25vbWljIGRldmVsb3BtZW50LCB3aGlsZSBsb25nLXRlcm0gb3JpZW50ZWQgY291bnRyaWVzIGNvbnRpbnVlIHRvIGRldmVsb3AgdG8gYSBwb2ludC4gDQoqICoqYGl2cjpgKiogIFRoaXMgZGltZW5zaW9uIGlzIGVzc2VudGlhbGx5IGEgbWVhc3VyZSBvZiBoYXBwaW5lc3M7IHdoZXRoZXIgb3Igbm90IHNpbXBsZSBqb3lzIGFyZSBmdWxmaWxsZWQuIEluZHVsZ2VuY2UgaXMgZGVmaW5lZCBhcyAiYSBzb2NpZXR5IHRoYXQgYWxsb3dzIHJlbGF0aXZlbHkgZnJlZSBncmF0aWZpY2F0aW9uIG9mIGJhc2ljIGFuZCBuYXR1cmFsIGh1bWFuIGRlc2lyZXMgcmVsYXRlZCB0byBlbmpveWluZyBsaWZlIGFuZCBoYXZpbmcgZnVuLiIgSXRzIGNvdW50ZXJwYXJ0IGlzIGRlZmluZWQgYXMgImEgc29jaWV0eSB0aGF0IGNvbnRyb2xzIGdyYXRpZmljYXRpb24gb2YgbmVlZHMgYW5kIHJlZ3VsYXRlcyBpdCBieSBtZWFucyBvZiBzdHJpY3Qgc29jaWFsIG5vcm1zLiBJbmR1bGdlbnQgc29jaWV0aWVzIGJlbGlldmUgdGhlbXNlbHZlcyB0byBiZSBpbiBjb250cm9sIG9mIHRoZWlyIG93biBsaWZlIGFuZCBlbW90aW9uczsgcmVzdHJhaW5lZCBzb2NpZXRpZXMgYmVsaWV2ZSBvdGhlciBmYWN0b3JzIGRpY3RhdGUgdGhlaXIgbGlmZSBhbmQgZW1vdGlvbnMNCg0KDQpPaywgbG9va3MgaW50ZXJlc3RpbmcuIExldCdzIGRvIHRoZSBmb2xvbHdpbmc6DQoNCjAuIFRoZSBkYXRhIGlzIG5vdCBwZXJmZWN0LiBTbyBzb21lIHNtYWxsIHVwZnJvbnQtbXVuZ2luZyBpcyBuZWNlc3NhcnkuDQoxLiBHZXJ0IEhvZnN0ZWRlIGNsYWltcyB0aGlzIGRpbWVuc2lvbnMgdG8gZW1hc3VyZSBvcnRob2dvbmFsIGZlYXR1cmVzIG9mIGN1bHR1cmUuIFRoYXQgcmFpc2VzIHRoZSBxdWVzdGlvbiBpZiB0aGV5IHJlYXN5IG1lYXN1cmUgZGlmZmVyZW50IGNvbnN0cnVjdHMuIFRvIGZpbmQgb3V0LCBsZXRzIGV4ZWN1dGUgYSBQQ0Egb24gdGhlbS4gSG93IGRvIHRoZSBkaW1lbnNpb25zIGxvYWQ/IEFuZCBob3cgZG8gY291bnRyaWVzIHNjb3JlPyBJbGx1c3RyYXRlIGFuZCB2aXN1YWxpemUgdGhlIHJlc3VsdHMuDQoyLiBDYW4gd2UgZm9ybSBtZWFuaW5nZnVsICJjdWx0dXJhbCBjbHVzdGVycyIgYW1vbmcgY291bnRyaWVzPw0KMy4gTGV0J3MgY3JlYXRlIGEgbWVhbmluZ2Z1bCBtZWFzdXJlIGZvciAiY3VsdHVyYWwgZGlzdGFuY2UiIGJldHdlZW4gY291bnRyaWVzLiBXaGF0IGRvIHdlIHNlZT8gSW50ZXJwcmV0Lg0KNC4gKEFkdmFuY2VkKSBEb2VzIGJpbGF0ZXJhbCAiY3VsdHVyYWwgZGlzdGFuY2UiIG9yIHRoZSBhc3NpZ25tZW50IHRvIGEgImN1bHR1cmFsIGNsdXN0ZXIiIGhlbHAgdXMgdG8gZXhwbGFpbiBvdGhlciBpbnRlcmFjdGlvbiBiZXR3ZWVuIGNvdW50cmllcyB3ZSBtaWdodCBiZSBpbnRlcmVzdGVkIGluLCBzdWNoIGFzIHRyYWRlLCBtaWdyYXRpb24gZXRjLj8gSGVyZSB5b3Ugd2lsbCBuZWVkIHNvbWUgc2tpbGxzIGZyb20gTTEtMSAmIDIuDQoNCkhhdmUgZnVuIQ0KDQo=